1013 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1013 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: (GPL-2.0 OR MIT)
 | |
| /* Microsemi Ocelot Switch driver
 | |
|  * Copyright (c) 2019 Microsemi Corporation
 | |
|  */
 | |
| 
 | |
| #include <net/pkt_cls.h>
 | |
| #include <net/tc_act/tc_gact.h>
 | |
| #include <soc/mscc/ocelot_vcap.h>
 | |
| #include "ocelot_police.h"
 | |
| #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))
 | |
| /* PSFP chain and block ID */
 | |
| #define PSFP_BLOCK_ID			OCELOT_NUM_VCAP_BLOCKS
 | |
| #define OCELOT_PSFP_CHAIN		(3 * VCAP_BLOCK)
 | |
| 
 | |
| 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;
 | |
| 
 | |
| 	if (chain == OCELOT_PSFP_CHAIN)
 | |
| 		return PSFP_BLOCK_ID;
 | |
| 
 | |
| 	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) ||
 | |
| 			goto_target == OCELOT_PSFP_CHAIN);
 | |
| 
 | |
| 	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 can goto to PSFP block if hardware support */
 | |
| 	for (pag = 0; pag < VCAP_IS2_NUM_PAG; pag++)
 | |
| 		if (chain == VCAP_IS2_CHAIN(1, pag))
 | |
| 			return (goto_target == OCELOT_PSFP_CHAIN);
 | |
| 
 | |
| 	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_ingress_vlan_modify(struct ocelot *ocelot, int port,
 | |
| 					struct ocelot_vcap_filter *filter,
 | |
| 					const struct flow_action_entry *a,
 | |
| 					struct netlink_ext_ack *extack)
 | |
| {
 | |
| 	struct ocelot_port *ocelot_port = ocelot->ports[port];
 | |
| 
 | |
| 	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;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int
 | |
| ocelot_flower_parse_egress_vlan_modify(struct ocelot_vcap_filter *filter,
 | |
| 				       const struct flow_action_entry *a,
 | |
| 				       struct netlink_ext_ack *extack)
 | |
| {
 | |
| 	enum ocelot_tag_tpid_sel tpid;
 | |
| 
 | |
| 	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 modify 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 = OCELOT_ES0_VID_PLUS_CLASSIFIED_VID;
 | |
| 	filter->action.vid_a_val = a->vlan.vid;
 | |
| 	filter->action.pcp_a_val = a->vlan.prio;
 | |
| 	filter->action.tag_a_pcp_sel = OCELOT_ES0_PCP;
 | |
| 	filter->type = OCELOT_VCAP_FILTER_OFFLOAD;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int ocelot_flower_parse_action(struct ocelot *ocelot, int port,
 | |
| 				      bool ingress, struct flow_cls_offload *f,
 | |
| 				      struct ocelot_vcap_filter *filter)
 | |
| {
 | |
| 	const struct flow_action *action = &f->rule->action;
 | |
| 	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;
 | |
| 	u32 pol_ix, pol_max;
 | |
| 	u64 rate;
 | |
| 	int err;
 | |
| 
 | |
| 	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, 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_ACCEPT:
 | |
| 			if (filter->block_id != VCAP_ES0 &&
 | |
| 			    filter->block_id != VCAP_IS1 &&
 | |
| 			    filter->block_id != VCAP_IS2) {
 | |
| 				NL_SET_ERR_MSG_MOD(extack,
 | |
| 						   "Accept action can only be offloaded to VCAP chains");
 | |
| 				return -EOPNOTSUPP;
 | |
| 			}
 | |
| 			if (filter->block_id != VCAP_ES0 &&
 | |
| 			    filter->goto_target != -1) {
 | |
| 				NL_SET_ERR_MSG_MOD(extack,
 | |
| 						   "Last action must be GOTO");
 | |
| 				return -EOPNOTSUPP;
 | |
| 			}
 | |
| 			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;
 | |
| 			filter->is_trap = true;
 | |
| 			break;
 | |
| 		case FLOW_ACTION_POLICE:
 | |
| 			if (filter->block_id == PSFP_BLOCK_ID) {
 | |
| 				filter->type = OCELOT_PSFP_FILTER_OFFLOAD;
 | |
| 				break;
 | |
| 			}
 | |
| 			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 or PSFP");
 | |
| 				return -EOPNOTSUPP;
 | |
| 			}
 | |
| 			if (filter->goto_target != -1) {
 | |
| 				NL_SET_ERR_MSG_MOD(extack,
 | |
| 						   "Last action must be GOTO");
 | |
| 				return -EOPNOTSUPP;
 | |
| 			}
 | |
| 
 | |
| 			err = ocelot_policer_validate(action, a, extack);
 | |
| 			if (err)
 | |
| 				return err;
 | |
| 
 | |
| 			filter->action.police_ena = true;
 | |
| 
 | |
| 			pol_ix = a->hw_index + ocelot->vcap_pol.base;
 | |
| 			pol_max = ocelot->vcap_pol.max;
 | |
| 
 | |
| 			if (ocelot->vcap_pol.max2 && pol_ix > pol_max) {
 | |
| 				pol_ix += ocelot->vcap_pol.base2 - pol_max - 1;
 | |
| 				pol_max = ocelot->vcap_pol.max2;
 | |
| 			}
 | |
| 
 | |
| 			if (pol_ix >= pol_max)
 | |
| 				return -EINVAL;
 | |
| 
 | |
| 			filter->action.pol_ix = pol_ix;
 | |
| 
 | |
| 			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_MIRRED:
 | |
| 			if (filter->block_id != VCAP_IS2) {
 | |
| 				NL_SET_ERR_MSG_MOD(extack,
 | |
| 						   "Mirror 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->egress_port.value = egress_port;
 | |
| 			filter->action.mirror_ena = true;
 | |
| 			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) {
 | |
| 				err = ocelot_flower_parse_ingress_vlan_modify(ocelot, port,
 | |
| 									      filter, a,
 | |
| 									      extack);
 | |
| 			} else if (filter->block_id == VCAP_ES0) {
 | |
| 				err = ocelot_flower_parse_egress_vlan_modify(filter, a,
 | |
| 									     extack);
 | |
| 			} else {
 | |
| 				NL_SET_ERR_MSG_MOD(extack,
 | |
| 						   "VLAN modify action can only be offloaded to VCAP IS1 or ES0");
 | |
| 				err = -EOPNOTSUPP;
 | |
| 			}
 | |
| 			if (err)
 | |
| 				return err;
 | |
| 			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 = OCELOT_ES0_VID;
 | |
| 			filter->action.vid_a_val = a->vlan.vid;
 | |
| 			filter->action.pcp_a_val = a->vlan.prio;
 | |
| 			filter->type = OCELOT_VCAP_FILTER_OFFLOAD;
 | |
| 			break;
 | |
| 		case FLOW_ACTION_GATE:
 | |
| 			if (filter->block_id != PSFP_BLOCK_ID) {
 | |
| 				NL_SET_ERR_MSG_MOD(extack,
 | |
| 						   "Gate action can only be offloaded to PSFP chain");
 | |
| 				return -EOPNOTSUPP;
 | |
| 			}
 | |
| 			filter->type = OCELOT_PSFP_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 || filter->block_id == PSFP_BLOCK_ID) {
 | |
| 			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_ULL(FLOW_DISSECTOR_KEY_CONTROL) |
 | |
| 	      BIT_ULL(FLOW_DISSECTOR_KEY_BASIC) |
 | |
| 	      BIT_ULL(FLOW_DISSECTOR_KEY_META) |
 | |
| 	      BIT_ULL(FLOW_DISSECTOR_KEY_PORTS) |
 | |
| 	      BIT_ULL(FLOW_DISSECTOR_KEY_VLAN) |
 | |
| 	      BIT_ULL(FLOW_DISSECTOR_KEY_IPV4_ADDRS) |
 | |
| 	      BIT_ULL(FLOW_DISSECTOR_KEY_IPV6_ADDRS) |
 | |
| 	      BIT_ULL(FLOW_DISSECTOR_KEY_ETH_ADDRS))) {
 | |
| 		return -EOPNOTSUPP;
 | |
| 	}
 | |
| 
 | |
| 	if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_META)) {
 | |
| 		struct flow_match_meta match;
 | |
| 
 | |
| 		flow_rule_match_meta(rule, &match);
 | |
| 		if (match.mask->l2_miss) {
 | |
| 			NL_SET_ERR_MSG_MOD(extack, "Can't match on \"l2_miss\"");
 | |
| 			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_has_control_flags(rule, extack))
 | |
| 		return -EOPNOTSUPP;
 | |
| 
 | |
| 	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_ULL(FLOW_DISSECTOR_KEY_ETH_ADDRS) |
 | |
| 		     BIT_ULL(FLOW_DISSECTOR_KEY_BASIC) |
 | |
| 		     BIT_ULL(FLOW_DISSECTOR_KEY_CONTROL))) !=
 | |
| 		    (BIT_ULL(FLOW_DISSECTOR_KEY_ETH_ADDRS) |
 | |
| 		     BIT_ULL(FLOW_DISSECTOR_KEY_BASIC) |
 | |
| 		     BIT_ULL(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.cookie = f->cookie;
 | |
| 	filter->id.tc_offload = true;
 | |
| 
 | |
| 	ret = ocelot_flower_parse_action(ocelot, port, ingress, f, filter);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	/* PSFP filter need to parse key by stream identification function. */
 | |
| 	if (filter->type == OCELOT_PSFP_FILTER_OFFLOAD)
 | |
| 		return 0;
 | |
| 
 | |
| 	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;
 | |
| }
 | |
| 
 | |
| /* If we have an egress VLAN modification rule, we need to actually write the
 | |
|  * delta between the input VLAN (from the key) and the output VLAN (from the
 | |
|  * action), but the action was parsed first. So we need to patch the delta into
 | |
|  * the action here.
 | |
|  */
 | |
| static int
 | |
| ocelot_flower_patch_es0_vlan_modify(struct ocelot_vcap_filter *filter,
 | |
| 				    struct netlink_ext_ack *extack)
 | |
| {
 | |
| 	if (filter->block_id != VCAP_ES0 ||
 | |
| 	    filter->action.tag_a_vid_sel != OCELOT_ES0_VID_PLUS_CLASSIFIED_VID)
 | |
| 		return 0;
 | |
| 
 | |
| 	if (filter->vlan.vid.mask != VLAN_VID_MASK) {
 | |
| 		NL_SET_ERR_MSG_MOD(extack,
 | |
| 				   "VCAP ES0 VLAN rewriting needs a full VLAN in the key");
 | |
| 		return -EOPNOTSUPP;
 | |
| 	}
 | |
| 
 | |
| 	filter->action.vid_a_val -= filter->vlan.vid.value;
 | |
| 	filter->action.vid_a_val &= VLAN_VID_MASK;
 | |
| 
 | |
| 	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 block_id, 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;
 | |
| 	}
 | |
| 
 | |
| 	block_id = ocelot_chain_to_block(chain, ingress);
 | |
| 	if (block_id < 0) {
 | |
| 		NL_SET_ERR_MSG_MOD(extack, "Cannot offload to this chain");
 | |
| 		return -EOPNOTSUPP;
 | |
| 	}
 | |
| 
 | |
| 	filter = ocelot_vcap_block_find_filter_by_id(&ocelot->block[block_id],
 | |
| 						     f->cookie, true);
 | |
| 	if (filter) {
 | |
| 		/* Filter already exists on other ports */
 | |
| 		if (!ingress) {
 | |
| 			NL_SET_ERR_MSG_MOD(extack, "VCAP ES0 does not support shared filters");
 | |
| 			return -EOPNOTSUPP;
 | |
| 		}
 | |
| 
 | |
| 		filter->ingress_port_mask |= BIT(port);
 | |
| 
 | |
| 		return ocelot_vcap_filter_replace(ocelot, filter);
 | |
| 	}
 | |
| 
 | |
| 	/* Filter didn't exist, create it now */
 | |
| 	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;
 | |
| 	}
 | |
| 
 | |
| 	ret = ocelot_flower_patch_es0_vlan_modify(filter, extack);
 | |
| 	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);
 | |
| 
 | |
| 	if (filter->type == OCELOT_PSFP_FILTER_OFFLOAD) {
 | |
| 		kfree(filter);
 | |
| 		if (ocelot->ops->psfp_filter_add)
 | |
| 			return ocelot->ops->psfp_filter_add(ocelot, port, f);
 | |
| 
 | |
| 		NL_SET_ERR_MSG_MOD(extack, "PSFP chain is not supported in HW");
 | |
| 		return -EOPNOTSUPP;
 | |
| 	}
 | |
| 
 | |
| 	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;
 | |
| 
 | |
| 	if (block_id == PSFP_BLOCK_ID) {
 | |
| 		if (ocelot->ops->psfp_filter_del)
 | |
| 			return ocelot->ops->psfp_filter_del(ocelot, f);
 | |
| 
 | |
| 		return -EOPNOTSUPP;
 | |
| 	}
 | |
| 
 | |
| 	block = &ocelot->block[block_id];
 | |
| 
 | |
| 	filter = ocelot_vcap_block_find_filter_by_id(block, f->cookie, true);
 | |
| 	if (!filter)
 | |
| 		return 0;
 | |
| 
 | |
| 	if (filter->type == OCELOT_VCAP_FILTER_DUMMY)
 | |
| 		return ocelot_vcap_dummy_filter_del(ocelot, filter);
 | |
| 
 | |
| 	if (ingress) {
 | |
| 		filter->ingress_port_mask &= ~BIT(port);
 | |
| 		if (filter->ingress_port_mask)
 | |
| 			return ocelot_vcap_filter_replace(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;
 | |
| 	struct flow_stats stats = {0};
 | |
| 	int block_id, ret;
 | |
| 
 | |
| 	block_id = ocelot_chain_to_block(f->common.chain_index, ingress);
 | |
| 	if (block_id < 0)
 | |
| 		return 0;
 | |
| 
 | |
| 	if (block_id == PSFP_BLOCK_ID) {
 | |
| 		if (ocelot->ops->psfp_stats_get) {
 | |
| 			ret = ocelot->ops->psfp_stats_get(ocelot, f, &stats);
 | |
| 			if (ret)
 | |
| 				return ret;
 | |
| 
 | |
| 			goto stats_update;
 | |
| 		}
 | |
| 
 | |
| 		return -EOPNOTSUPP;
 | |
| 	}
 | |
| 
 | |
| 	block = &ocelot->block[block_id];
 | |
| 
 | |
| 	filter = ocelot_vcap_block_find_filter_by_id(block, f->cookie, true);
 | |
| 	if (!filter || filter->type == OCELOT_VCAP_FILTER_DUMMY)
 | |
| 		return 0;
 | |
| 
 | |
| 	ret = ocelot_vcap_filter_stats_update(ocelot, filter);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	stats.pkts = filter->stats.pkts;
 | |
| 
 | |
| stats_update:
 | |
| 	flow_stats_update(&f->stats, 0x0, stats.pkts, stats.drops, 0x0,
 | |
| 			  FLOW_ACTION_HW_STATS_IMMEDIATE);
 | |
| 	return 0;
 | |
| }
 | |
| EXPORT_SYMBOL_GPL(ocelot_cls_flower_stats);
 |