// SPDX-License-Identifier: (GPL-2.0 OR MIT) /* Microsemi Ocelot Switch driver * Copyright (c) 2019 Microsemi Corporation */ #include #include #include #include "ocelot_vcap.h" /* Arbitrarily chosen constants for encoding the VCAP block and lookup number * into the chain number. This is UAPI. */ #define VCAP_BLOCK 10000 #define VCAP_LOOKUP 1000 #define VCAP_IS1_NUM_LOOKUPS 3 #define VCAP_IS2_NUM_LOOKUPS 2 #define VCAP_IS2_NUM_PAG 256 #define VCAP_IS1_CHAIN(lookup) \ (1 * VCAP_BLOCK + (lookup) * VCAP_LOOKUP) #define VCAP_IS2_CHAIN(lookup, pag) \ (2 * VCAP_BLOCK + (lookup) * VCAP_LOOKUP + (pag)) static int ocelot_chain_to_block(int chain, bool ingress) { int lookup, pag; if (!ingress) { if (chain == 0) return VCAP_ES0; return -EOPNOTSUPP; } /* Backwards compatibility with older, single-chain tc-flower * offload support in Ocelot */ if (chain == 0) return VCAP_IS2; for (lookup = 0; lookup < VCAP_IS1_NUM_LOOKUPS; lookup++) if (chain == VCAP_IS1_CHAIN(lookup)) return VCAP_IS1; for (lookup = 0; lookup < VCAP_IS2_NUM_LOOKUPS; lookup++) for (pag = 0; pag < VCAP_IS2_NUM_PAG; pag++) if (chain == VCAP_IS2_CHAIN(lookup, pag)) return VCAP_IS2; return -EOPNOTSUPP; } /* Caller must ensure this is a valid IS1 or IS2 chain first, * by calling ocelot_chain_to_block. */ static int ocelot_chain_to_lookup(int chain) { /* Backwards compatibility with older, single-chain tc-flower * offload support in Ocelot */ if (chain == 0) return 0; return (chain / VCAP_LOOKUP) % 10; } /* Caller must ensure this is a valid IS2 chain first, * by calling ocelot_chain_to_block. */ static int ocelot_chain_to_pag(int chain) { int lookup; /* Backwards compatibility with older, single-chain tc-flower * offload support in Ocelot */ if (chain == 0) return 0; lookup = ocelot_chain_to_lookup(chain); /* calculate PAG value as chain index relative to the first PAG */ return chain - VCAP_IS2_CHAIN(lookup, 0); } static bool ocelot_is_goto_target_valid(int goto_target, int chain, bool ingress) { int pag; /* Can't offload GOTO in VCAP ES0 */ if (!ingress) return (goto_target < 0); /* Non-optional GOTOs */ if (chain == 0) /* VCAP IS1 can be skipped, either partially or completely */ return (goto_target == VCAP_IS1_CHAIN(0) || goto_target == VCAP_IS1_CHAIN(1) || goto_target == VCAP_IS1_CHAIN(2) || goto_target == VCAP_IS2_CHAIN(0, 0) || goto_target == VCAP_IS2_CHAIN(1, 0)); if (chain == VCAP_IS1_CHAIN(0)) return (goto_target == VCAP_IS1_CHAIN(1)); if (chain == VCAP_IS1_CHAIN(1)) return (goto_target == VCAP_IS1_CHAIN(2)); /* Lookup 2 of VCAP IS1 can really support non-optional GOTOs, * using a Policy Association Group (PAG) value, which is an 8-bit * value encoding a VCAP IS2 target chain. */ if (chain == VCAP_IS1_CHAIN(2)) { for (pag = 0; pag < VCAP_IS2_NUM_PAG; pag++) if (goto_target == VCAP_IS2_CHAIN(0, pag)) return true; return false; } /* Non-optional GOTO from VCAP IS2 lookup 0 to lookup 1. * We cannot change the PAG at this point. */ for (pag = 0; pag < VCAP_IS2_NUM_PAG; pag++) if (chain == VCAP_IS2_CHAIN(0, pag)) return (goto_target == VCAP_IS2_CHAIN(1, pag)); /* VCAP IS2 lookup 1 cannot jump anywhere */ return false; } static struct ocelot_vcap_filter * ocelot_find_vcap_filter_that_points_at(struct ocelot *ocelot, int chain) { struct ocelot_vcap_filter *filter; struct ocelot_vcap_block *block; int block_id; block_id = ocelot_chain_to_block(chain, true); if (block_id < 0) return NULL; if (block_id == VCAP_IS2) { block = &ocelot->block[VCAP_IS1]; list_for_each_entry(filter, &block->rules, list) if (filter->type == OCELOT_VCAP_FILTER_PAG && filter->goto_target == chain) return filter; } list_for_each_entry(filter, &ocelot->dummy_rules, list) if (filter->goto_target == chain) return filter; return NULL; } static int ocelot_flower_parse_action(struct ocelot *ocelot, int port, bool ingress, struct flow_cls_offload *f, struct ocelot_vcap_filter *filter) { struct ocelot_port *ocelot_port = ocelot->ports[port]; struct netlink_ext_ack *extack = f->common.extack; bool allow_missing_goto_target = false; const struct flow_action_entry *a; enum ocelot_tag_tpid_sel tpid; int i, chain, egress_port; u64 rate; if (!flow_action_basic_hw_stats_check(&f->rule->action, f->common.extack)) return -EOPNOTSUPP; chain = f->common.chain_index; filter->block_id = ocelot_chain_to_block(chain, ingress); if (filter->block_id < 0) { NL_SET_ERR_MSG_MOD(extack, "Cannot offload to this chain"); return -EOPNOTSUPP; } if (filter->block_id == VCAP_IS1 || filter->block_id == VCAP_IS2) filter->lookup = ocelot_chain_to_lookup(chain); if (filter->block_id == VCAP_IS2) filter->pag = ocelot_chain_to_pag(chain); filter->goto_target = -1; filter->type = OCELOT_VCAP_FILTER_DUMMY; flow_action_for_each(i, a, &f->rule->action) { switch (a->id) { case FLOW_ACTION_DROP: if (filter->block_id != VCAP_IS2) { NL_SET_ERR_MSG_MOD(extack, "Drop action can only be offloaded to VCAP IS2"); return -EOPNOTSUPP; } if (filter->goto_target != -1) { NL_SET_ERR_MSG_MOD(extack, "Last action must be GOTO"); return -EOPNOTSUPP; } filter->action.mask_mode = OCELOT_MASK_MODE_PERMIT_DENY; filter->action.port_mask = 0; filter->action.police_ena = true; filter->action.pol_ix = OCELOT_POLICER_DISCARD; filter->type = OCELOT_VCAP_FILTER_OFFLOAD; break; case FLOW_ACTION_TRAP: if (filter->block_id != VCAP_IS2 || filter->lookup != 0) { NL_SET_ERR_MSG_MOD(extack, "Trap action can only be offloaded to VCAP IS2 lookup 0"); return -EOPNOTSUPP; } if (filter->goto_target != -1) { NL_SET_ERR_MSG_MOD(extack, "Last action must be GOTO"); return -EOPNOTSUPP; } filter->action.mask_mode = OCELOT_MASK_MODE_PERMIT_DENY; filter->action.port_mask = 0; filter->action.cpu_copy_ena = true; filter->action.cpu_qu_num = 0; filter->type = OCELOT_VCAP_FILTER_OFFLOAD; break; case FLOW_ACTION_POLICE: if (filter->block_id != VCAP_IS2 || filter->lookup != 0) { NL_SET_ERR_MSG_MOD(extack, "Police action can only be offloaded to VCAP IS2 lookup 0"); return -EOPNOTSUPP; } if (filter->goto_target != -1) { NL_SET_ERR_MSG_MOD(extack, "Last action must be GOTO"); return -EOPNOTSUPP; } filter->action.police_ena = true; rate = a->police.rate_bytes_ps; filter->action.pol.rate = div_u64(rate, 1000) * 8; filter->action.pol.burst = a->police.burst; filter->type = OCELOT_VCAP_FILTER_OFFLOAD; break; case FLOW_ACTION_REDIRECT: if (filter->block_id != VCAP_IS2) { NL_SET_ERR_MSG_MOD(extack, "Redirect action can only be offloaded to VCAP IS2"); return -EOPNOTSUPP; } if (filter->goto_target != -1) { NL_SET_ERR_MSG_MOD(extack, "Last action must be GOTO"); return -EOPNOTSUPP; } egress_port = ocelot->ops->netdev_to_port(a->dev); if (egress_port < 0) { NL_SET_ERR_MSG_MOD(extack, "Destination not an ocelot port"); return -EOPNOTSUPP; } filter->action.mask_mode = OCELOT_MASK_MODE_REDIRECT; filter->action.port_mask = BIT(egress_port); filter->type = OCELOT_VCAP_FILTER_OFFLOAD; break; case FLOW_ACTION_VLAN_POP: if (filter->block_id != VCAP_IS1) { NL_SET_ERR_MSG_MOD(extack, "VLAN pop action can only be offloaded to VCAP IS1"); return -EOPNOTSUPP; } if (filter->goto_target != -1) { NL_SET_ERR_MSG_MOD(extack, "Last action must be GOTO"); return -EOPNOTSUPP; } filter->action.vlan_pop_cnt_ena = true; filter->action.vlan_pop_cnt++; if (filter->action.vlan_pop_cnt > 2) { NL_SET_ERR_MSG_MOD(extack, "Cannot pop more than 2 VLAN headers"); return -EOPNOTSUPP; } filter->type = OCELOT_VCAP_FILTER_OFFLOAD; break; case FLOW_ACTION_VLAN_MANGLE: if (filter->block_id != VCAP_IS1) { NL_SET_ERR_MSG_MOD(extack, "VLAN modify action can only be offloaded to VCAP IS1"); return -EOPNOTSUPP; } if (filter->goto_target != -1) { NL_SET_ERR_MSG_MOD(extack, "Last action must be GOTO"); return -EOPNOTSUPP; } if (!ocelot_port->vlan_aware) { NL_SET_ERR_MSG_MOD(extack, "Can only modify VLAN under VLAN aware bridge"); return -EOPNOTSUPP; } filter->action.vid_replace_ena = true; filter->action.pcp_dei_ena = true; filter->action.vid = a->vlan.vid; filter->action.pcp = a->vlan.prio; filter->type = OCELOT_VCAP_FILTER_OFFLOAD; break; case FLOW_ACTION_PRIORITY: if (filter->block_id != VCAP_IS1) { NL_SET_ERR_MSG_MOD(extack, "Priority action can only be offloaded to VCAP IS1"); return -EOPNOTSUPP; } if (filter->goto_target != -1) { NL_SET_ERR_MSG_MOD(extack, "Last action must be GOTO"); return -EOPNOTSUPP; } filter->action.qos_ena = true; filter->action.qos_val = a->priority; filter->type = OCELOT_VCAP_FILTER_OFFLOAD; break; case FLOW_ACTION_GOTO: filter->goto_target = a->chain_index; if (filter->block_id == VCAP_IS1 && filter->lookup == 2) { int pag = ocelot_chain_to_pag(filter->goto_target); filter->action.pag_override_mask = 0xff; filter->action.pag_val = pag; filter->type = OCELOT_VCAP_FILTER_PAG; } break; case FLOW_ACTION_VLAN_PUSH: if (filter->block_id != VCAP_ES0) { NL_SET_ERR_MSG_MOD(extack, "VLAN push action can only be offloaded to VCAP ES0"); return -EOPNOTSUPP; } switch (ntohs(a->vlan.proto)) { case ETH_P_8021Q: tpid = OCELOT_TAG_TPID_SEL_8021Q; break; case ETH_P_8021AD: tpid = OCELOT_TAG_TPID_SEL_8021AD; break; default: NL_SET_ERR_MSG_MOD(extack, "Cannot push custom TPID"); return -EOPNOTSUPP; } filter->action.tag_a_tpid_sel = tpid; filter->action.push_outer_tag = OCELOT_ES0_TAG; filter->action.tag_a_vid_sel = 1; filter->action.vid_a_val = a->vlan.vid; filter->action.pcp_a_val = a->vlan.prio; filter->type = OCELOT_VCAP_FILTER_OFFLOAD; break; default: NL_SET_ERR_MSG_MOD(extack, "Cannot offload action"); return -EOPNOTSUPP; } } if (filter->goto_target == -1) { if ((filter->block_id == VCAP_IS2 && filter->lookup == 1) || chain == 0) { allow_missing_goto_target = true; } else { NL_SET_ERR_MSG_MOD(extack, "Missing GOTO action"); return -EOPNOTSUPP; } } if (!ocelot_is_goto_target_valid(filter->goto_target, chain, ingress) && !allow_missing_goto_target) { NL_SET_ERR_MSG_MOD(extack, "Cannot offload this GOTO target"); return -EOPNOTSUPP; } return 0; } static int ocelot_flower_parse_indev(struct ocelot *ocelot, int port, struct flow_cls_offload *f, struct ocelot_vcap_filter *filter) { struct flow_rule *rule = flow_cls_offload_flow_rule(f); const struct vcap_props *vcap = &ocelot->vcap[VCAP_ES0]; int key_length = vcap->keys[VCAP_ES0_IGR_PORT].length; struct netlink_ext_ack *extack = f->common.extack; struct net_device *dev, *indev; struct flow_match_meta match; int ingress_port; flow_rule_match_meta(rule, &match); if (!match.mask->ingress_ifindex) return 0; if (match.mask->ingress_ifindex != 0xFFFFFFFF) { NL_SET_ERR_MSG_MOD(extack, "Unsupported ingress ifindex mask"); return -EOPNOTSUPP; } dev = ocelot->ops->port_to_netdev(ocelot, port); if (!dev) return -EINVAL; indev = __dev_get_by_index(dev_net(dev), match.key->ingress_ifindex); if (!indev) { NL_SET_ERR_MSG_MOD(extack, "Can't find the ingress port to match on"); return -ENOENT; } ingress_port = ocelot->ops->netdev_to_port(indev); if (ingress_port < 0) { NL_SET_ERR_MSG_MOD(extack, "Can only offload an ocelot ingress port"); return -EOPNOTSUPP; } if (ingress_port == port) { NL_SET_ERR_MSG_MOD(extack, "Ingress port is equal to the egress port"); return -EINVAL; } filter->ingress_port.value = ingress_port; filter->ingress_port.mask = GENMASK(key_length - 1, 0); return 0; } static int ocelot_flower_parse_key(struct ocelot *ocelot, int port, bool ingress, struct flow_cls_offload *f, struct ocelot_vcap_filter *filter) { struct flow_rule *rule = flow_cls_offload_flow_rule(f); struct flow_dissector *dissector = rule->match.dissector; struct netlink_ext_ack *extack = f->common.extack; u16 proto = ntohs(f->common.protocol); bool match_protocol = true; int ret; if (dissector->used_keys & ~(BIT(FLOW_DISSECTOR_KEY_CONTROL) | BIT(FLOW_DISSECTOR_KEY_BASIC) | BIT(FLOW_DISSECTOR_KEY_META) | BIT(FLOW_DISSECTOR_KEY_PORTS) | BIT(FLOW_DISSECTOR_KEY_VLAN) | BIT(FLOW_DISSECTOR_KEY_IPV4_ADDRS) | BIT(FLOW_DISSECTOR_KEY_IPV6_ADDRS) | BIT(FLOW_DISSECTOR_KEY_ETH_ADDRS))) { return -EOPNOTSUPP; } /* For VCAP ES0 (egress rewriter) we can match on the ingress port */ if (!ingress) { ret = ocelot_flower_parse_indev(ocelot, port, f, filter); if (ret) return ret; } if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_CONTROL)) { struct flow_match_control match; flow_rule_match_control(rule, &match); } if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_VLAN)) { struct flow_match_vlan match; flow_rule_match_vlan(rule, &match); filter->key_type = OCELOT_VCAP_KEY_ANY; filter->vlan.vid.value = match.key->vlan_id; filter->vlan.vid.mask = match.mask->vlan_id; filter->vlan.pcp.value[0] = match.key->vlan_priority; filter->vlan.pcp.mask[0] = match.mask->vlan_priority; match_protocol = false; } if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_ETH_ADDRS)) { struct flow_match_eth_addrs match; if (filter->block_id == VCAP_ES0) { NL_SET_ERR_MSG_MOD(extack, "VCAP ES0 cannot match on MAC address"); return -EOPNOTSUPP; } /* The hw support mac matches only for MAC_ETYPE key, * therefore if other matches(port, tcp flags, etc) are added * then just bail out */ if ((dissector->used_keys & (BIT(FLOW_DISSECTOR_KEY_ETH_ADDRS) | BIT(FLOW_DISSECTOR_KEY_BASIC) | BIT(FLOW_DISSECTOR_KEY_CONTROL))) != (BIT(FLOW_DISSECTOR_KEY_ETH_ADDRS) | BIT(FLOW_DISSECTOR_KEY_BASIC) | BIT(FLOW_DISSECTOR_KEY_CONTROL))) return -EOPNOTSUPP; flow_rule_match_eth_addrs(rule, &match); if (filter->block_id == VCAP_IS1 && !is_zero_ether_addr(match.mask->dst)) { NL_SET_ERR_MSG_MOD(extack, "Key type S1_NORMAL cannot match on destination MAC"); return -EOPNOTSUPP; } filter->key_type = OCELOT_VCAP_KEY_ETYPE; ether_addr_copy(filter->key.etype.dmac.value, match.key->dst); ether_addr_copy(filter->key.etype.smac.value, match.key->src); ether_addr_copy(filter->key.etype.dmac.mask, match.mask->dst); ether_addr_copy(filter->key.etype.smac.mask, match.mask->src); goto finished_key_parsing; } if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_BASIC)) { struct flow_match_basic match; flow_rule_match_basic(rule, &match); if (ntohs(match.key->n_proto) == ETH_P_IP) { if (filter->block_id == VCAP_ES0) { NL_SET_ERR_MSG_MOD(extack, "VCAP ES0 cannot match on IP protocol"); return -EOPNOTSUPP; } filter->key_type = OCELOT_VCAP_KEY_IPV4; filter->key.ipv4.proto.value[0] = match.key->ip_proto; filter->key.ipv4.proto.mask[0] = match.mask->ip_proto; match_protocol = false; } if (ntohs(match.key->n_proto) == ETH_P_IPV6) { if (filter->block_id == VCAP_ES0) { NL_SET_ERR_MSG_MOD(extack, "VCAP ES0 cannot match on IP protocol"); return -EOPNOTSUPP; } filter->key_type = OCELOT_VCAP_KEY_IPV6; filter->key.ipv6.proto.value[0] = match.key->ip_proto; filter->key.ipv6.proto.mask[0] = match.mask->ip_proto; match_protocol = false; } } if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_IPV4_ADDRS) && proto == ETH_P_IP) { struct flow_match_ipv4_addrs match; u8 *tmp; if (filter->block_id == VCAP_ES0) { NL_SET_ERR_MSG_MOD(extack, "VCAP ES0 cannot match on IP address"); return -EOPNOTSUPP; } flow_rule_match_ipv4_addrs(rule, &match); if (filter->block_id == VCAP_IS1 && *(u32 *)&match.mask->dst) { NL_SET_ERR_MSG_MOD(extack, "Key type S1_NORMAL cannot match on destination IP"); return -EOPNOTSUPP; } tmp = &filter->key.ipv4.sip.value.addr[0]; memcpy(tmp, &match.key->src, 4); tmp = &filter->key.ipv4.sip.mask.addr[0]; memcpy(tmp, &match.mask->src, 4); tmp = &filter->key.ipv4.dip.value.addr[0]; memcpy(tmp, &match.key->dst, 4); tmp = &filter->key.ipv4.dip.mask.addr[0]; memcpy(tmp, &match.mask->dst, 4); match_protocol = false; } if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_IPV6_ADDRS) && proto == ETH_P_IPV6) { return -EOPNOTSUPP; } if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_PORTS)) { struct flow_match_ports match; if (filter->block_id == VCAP_ES0) { NL_SET_ERR_MSG_MOD(extack, "VCAP ES0 cannot match on L4 ports"); return -EOPNOTSUPP; } flow_rule_match_ports(rule, &match); filter->key.ipv4.sport.value = ntohs(match.key->src); filter->key.ipv4.sport.mask = ntohs(match.mask->src); filter->key.ipv4.dport.value = ntohs(match.key->dst); filter->key.ipv4.dport.mask = ntohs(match.mask->dst); match_protocol = false; } finished_key_parsing: if (match_protocol && proto != ETH_P_ALL) { if (filter->block_id == VCAP_ES0) { NL_SET_ERR_MSG_MOD(extack, "VCAP ES0 cannot match on L2 proto"); return -EOPNOTSUPP; } /* TODO: support SNAP, LLC etc */ if (proto < ETH_P_802_3_MIN) return -EOPNOTSUPP; filter->key_type = OCELOT_VCAP_KEY_ETYPE; *(__be16 *)filter->key.etype.etype.value = htons(proto); *(__be16 *)filter->key.etype.etype.mask = htons(0xffff); } /* else, a filter of type OCELOT_VCAP_KEY_ANY is implicitly added */ return 0; } static int ocelot_flower_parse(struct ocelot *ocelot, int port, bool ingress, struct flow_cls_offload *f, struct ocelot_vcap_filter *filter) { int ret; filter->prio = f->common.prio; filter->id = f->cookie; ret = ocelot_flower_parse_action(ocelot, port, ingress, f, filter); if (ret) return ret; return ocelot_flower_parse_key(ocelot, port, ingress, f, filter); } static struct ocelot_vcap_filter *ocelot_vcap_filter_create(struct ocelot *ocelot, int port, bool ingress, struct flow_cls_offload *f) { struct ocelot_vcap_filter *filter; filter = kzalloc(sizeof(*filter), GFP_KERNEL); if (!filter) return NULL; if (ingress) { filter->ingress_port_mask = BIT(port); } else { const struct vcap_props *vcap = &ocelot->vcap[VCAP_ES0]; int key_length = vcap->keys[VCAP_ES0_EGR_PORT].length; filter->egress_port.value = port; filter->egress_port.mask = GENMASK(key_length - 1, 0); } return filter; } static int ocelot_vcap_dummy_filter_add(struct ocelot *ocelot, struct ocelot_vcap_filter *filter) { list_add(&filter->list, &ocelot->dummy_rules); return 0; } static int ocelot_vcap_dummy_filter_del(struct ocelot *ocelot, struct ocelot_vcap_filter *filter) { list_del(&filter->list); kfree(filter); return 0; } int ocelot_cls_flower_replace(struct ocelot *ocelot, int port, struct flow_cls_offload *f, bool ingress) { struct netlink_ext_ack *extack = f->common.extack; struct ocelot_vcap_filter *filter; int chain = f->common.chain_index; int ret; if (chain && !ocelot_find_vcap_filter_that_points_at(ocelot, chain)) { NL_SET_ERR_MSG_MOD(extack, "No default GOTO action points to this chain"); return -EOPNOTSUPP; } filter = ocelot_vcap_filter_create(ocelot, port, ingress, f); if (!filter) return -ENOMEM; ret = ocelot_flower_parse(ocelot, port, ingress, f, filter); if (ret) { kfree(filter); return ret; } /* The non-optional GOTOs for the TCAM skeleton don't need * to be actually offloaded. */ if (filter->type == OCELOT_VCAP_FILTER_DUMMY) return ocelot_vcap_dummy_filter_add(ocelot, filter); return ocelot_vcap_filter_add(ocelot, filter, f->common.extack); } EXPORT_SYMBOL_GPL(ocelot_cls_flower_replace); int ocelot_cls_flower_destroy(struct ocelot *ocelot, int port, struct flow_cls_offload *f, bool ingress) { struct ocelot_vcap_filter *filter; struct ocelot_vcap_block *block; int block_id; block_id = ocelot_chain_to_block(f->common.chain_index, ingress); if (block_id < 0) return 0; block = &ocelot->block[block_id]; filter = ocelot_vcap_block_find_filter_by_id(block, f->cookie); if (!filter) return 0; if (filter->type == OCELOT_VCAP_FILTER_DUMMY) return ocelot_vcap_dummy_filter_del(ocelot, filter); return ocelot_vcap_filter_del(ocelot, filter); } EXPORT_SYMBOL_GPL(ocelot_cls_flower_destroy); int ocelot_cls_flower_stats(struct ocelot *ocelot, int port, struct flow_cls_offload *f, bool ingress) { struct ocelot_vcap_filter *filter; struct ocelot_vcap_block *block; int block_id, ret; block_id = ocelot_chain_to_block(f->common.chain_index, ingress); if (block_id < 0) return 0; block = &ocelot->block[block_id]; filter = ocelot_vcap_block_find_filter_by_id(block, f->cookie); if (!filter || filter->type == OCELOT_VCAP_FILTER_DUMMY) return 0; ret = ocelot_vcap_filter_stats_update(ocelot, filter); if (ret) return ret; flow_stats_update(&f->stats, 0x0, filter->stats.pkts, 0, 0x0, FLOW_ACTION_HW_STATS_IMMEDIATE); return 0; } EXPORT_SYMBOL_GPL(ocelot_cls_flower_stats);