| // SPDX-License-Identifier: GPL-2.0 | 
 | /* | 
 |  * DPAA2 Ethernet Switch flower support | 
 |  * | 
 |  * Copyright 2021 NXP | 
 |  * | 
 |  */ | 
 |  | 
 | #include "dpaa2-switch.h" | 
 |  | 
 | static int dpaa2_switch_flower_parse_key(struct flow_cls_offload *cls, | 
 | 					 struct dpsw_acl_key *acl_key) | 
 | { | 
 | 	struct flow_rule *rule = flow_cls_offload_flow_rule(cls); | 
 | 	struct flow_dissector *dissector = rule->match.dissector; | 
 | 	struct netlink_ext_ack *extack = cls->common.extack; | 
 | 	struct dpsw_acl_fields *acl_h, *acl_m; | 
 |  | 
 | 	if (dissector->used_keys & | 
 | 	    ~(BIT(FLOW_DISSECTOR_KEY_BASIC) | | 
 | 	      BIT(FLOW_DISSECTOR_KEY_CONTROL) | | 
 | 	      BIT(FLOW_DISSECTOR_KEY_ETH_ADDRS) | | 
 | 	      BIT(FLOW_DISSECTOR_KEY_VLAN) | | 
 | 	      BIT(FLOW_DISSECTOR_KEY_PORTS) | | 
 | 	      BIT(FLOW_DISSECTOR_KEY_IP) | | 
 | 	      BIT(FLOW_DISSECTOR_KEY_IPV6_ADDRS) | | 
 | 	      BIT(FLOW_DISSECTOR_KEY_IPV4_ADDRS))) { | 
 | 		NL_SET_ERR_MSG_MOD(extack, | 
 | 				   "Unsupported keys used"); | 
 | 		return -EOPNOTSUPP; | 
 | 	} | 
 |  | 
 | 	acl_h = &acl_key->match; | 
 | 	acl_m = &acl_key->mask; | 
 |  | 
 | 	if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_BASIC)) { | 
 | 		struct flow_match_basic match; | 
 |  | 
 | 		flow_rule_match_basic(rule, &match); | 
 | 		acl_h->l3_protocol = match.key->ip_proto; | 
 | 		acl_h->l2_ether_type = be16_to_cpu(match.key->n_proto); | 
 | 		acl_m->l3_protocol = match.mask->ip_proto; | 
 | 		acl_m->l2_ether_type = be16_to_cpu(match.mask->n_proto); | 
 | 	} | 
 |  | 
 | 	if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_ETH_ADDRS)) { | 
 | 		struct flow_match_eth_addrs match; | 
 |  | 
 | 		flow_rule_match_eth_addrs(rule, &match); | 
 | 		ether_addr_copy(acl_h->l2_dest_mac, &match.key->dst[0]); | 
 | 		ether_addr_copy(acl_h->l2_source_mac, &match.key->src[0]); | 
 | 		ether_addr_copy(acl_m->l2_dest_mac, &match.mask->dst[0]); | 
 | 		ether_addr_copy(acl_m->l2_source_mac, &match.mask->src[0]); | 
 | 	} | 
 |  | 
 | 	if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_VLAN)) { | 
 | 		struct flow_match_vlan match; | 
 |  | 
 | 		flow_rule_match_vlan(rule, &match); | 
 | 		acl_h->l2_vlan_id = match.key->vlan_id; | 
 | 		acl_h->l2_tpid = be16_to_cpu(match.key->vlan_tpid); | 
 | 		acl_h->l2_pcp_dei = match.key->vlan_priority << 1 | | 
 | 				    match.key->vlan_dei; | 
 |  | 
 | 		acl_m->l2_vlan_id = match.mask->vlan_id; | 
 | 		acl_m->l2_tpid = be16_to_cpu(match.mask->vlan_tpid); | 
 | 		acl_m->l2_pcp_dei = match.mask->vlan_priority << 1 | | 
 | 				    match.mask->vlan_dei; | 
 | 	} | 
 |  | 
 | 	if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_IPV4_ADDRS)) { | 
 | 		struct flow_match_ipv4_addrs match; | 
 |  | 
 | 		flow_rule_match_ipv4_addrs(rule, &match); | 
 | 		acl_h->l3_source_ip = be32_to_cpu(match.key->src); | 
 | 		acl_h->l3_dest_ip = be32_to_cpu(match.key->dst); | 
 | 		acl_m->l3_source_ip = be32_to_cpu(match.mask->src); | 
 | 		acl_m->l3_dest_ip = be32_to_cpu(match.mask->dst); | 
 | 	} | 
 |  | 
 | 	if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_PORTS)) { | 
 | 		struct flow_match_ports match; | 
 |  | 
 | 		flow_rule_match_ports(rule, &match); | 
 | 		acl_h->l4_source_port = be16_to_cpu(match.key->src); | 
 | 		acl_h->l4_dest_port = be16_to_cpu(match.key->dst); | 
 | 		acl_m->l4_source_port = be16_to_cpu(match.mask->src); | 
 | 		acl_m->l4_dest_port = be16_to_cpu(match.mask->dst); | 
 | 	} | 
 |  | 
 | 	if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_IP)) { | 
 | 		struct flow_match_ip match; | 
 |  | 
 | 		flow_rule_match_ip(rule, &match); | 
 | 		if (match.mask->ttl != 0) { | 
 | 			NL_SET_ERR_MSG_MOD(extack, | 
 | 					   "Matching on TTL not supported"); | 
 | 			return -EOPNOTSUPP; | 
 | 		} | 
 |  | 
 | 		if ((match.mask->tos & 0x3) != 0) { | 
 | 			NL_SET_ERR_MSG_MOD(extack, | 
 | 					   "Matching on ECN not supported, only DSCP"); | 
 | 			return -EOPNOTSUPP; | 
 | 		} | 
 |  | 
 | 		acl_h->l3_dscp = match.key->tos >> 2; | 
 | 		acl_m->l3_dscp = match.mask->tos >> 2; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | int dpaa2_switch_acl_entry_add(struct dpaa2_switch_filter_block *filter_block, | 
 | 			       struct dpaa2_switch_acl_entry *entry) | 
 | { | 
 | 	struct dpsw_acl_entry_cfg *acl_entry_cfg = &entry->cfg; | 
 | 	struct ethsw_core *ethsw = filter_block->ethsw; | 
 | 	struct dpsw_acl_key *acl_key = &entry->key; | 
 | 	struct device *dev = ethsw->dev; | 
 | 	u8 *cmd_buff; | 
 | 	int err; | 
 |  | 
 | 	cmd_buff = kzalloc(DPAA2_ETHSW_PORT_ACL_CMD_BUF_SIZE, GFP_KERNEL); | 
 | 	if (!cmd_buff) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	dpsw_acl_prepare_entry_cfg(acl_key, cmd_buff); | 
 |  | 
 | 	acl_entry_cfg->key_iova = dma_map_single(dev, cmd_buff, | 
 | 						 DPAA2_ETHSW_PORT_ACL_CMD_BUF_SIZE, | 
 | 						 DMA_TO_DEVICE); | 
 | 	if (unlikely(dma_mapping_error(dev, acl_entry_cfg->key_iova))) { | 
 | 		dev_err(dev, "DMA mapping failed\n"); | 
 | 		return -EFAULT; | 
 | 	} | 
 |  | 
 | 	err = dpsw_acl_add_entry(ethsw->mc_io, 0, ethsw->dpsw_handle, | 
 | 				 filter_block->acl_id, acl_entry_cfg); | 
 |  | 
 | 	dma_unmap_single(dev, acl_entry_cfg->key_iova, sizeof(cmd_buff), | 
 | 			 DMA_TO_DEVICE); | 
 | 	if (err) { | 
 | 		dev_err(dev, "dpsw_acl_add_entry() failed %d\n", err); | 
 | 		return err; | 
 | 	} | 
 |  | 
 | 	kfree(cmd_buff); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int | 
 | dpaa2_switch_acl_entry_remove(struct dpaa2_switch_filter_block *block, | 
 | 			      struct dpaa2_switch_acl_entry *entry) | 
 | { | 
 | 	struct dpsw_acl_entry_cfg *acl_entry_cfg = &entry->cfg; | 
 | 	struct dpsw_acl_key *acl_key = &entry->key; | 
 | 	struct ethsw_core *ethsw = block->ethsw; | 
 | 	struct device *dev = ethsw->dev; | 
 | 	u8 *cmd_buff; | 
 | 	int err; | 
 |  | 
 | 	cmd_buff = kzalloc(DPAA2_ETHSW_PORT_ACL_CMD_BUF_SIZE, GFP_KERNEL); | 
 | 	if (!cmd_buff) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	dpsw_acl_prepare_entry_cfg(acl_key, cmd_buff); | 
 |  | 
 | 	acl_entry_cfg->key_iova = dma_map_single(dev, cmd_buff, | 
 | 						 DPAA2_ETHSW_PORT_ACL_CMD_BUF_SIZE, | 
 | 						 DMA_TO_DEVICE); | 
 | 	if (unlikely(dma_mapping_error(dev, acl_entry_cfg->key_iova))) { | 
 | 		dev_err(dev, "DMA mapping failed\n"); | 
 | 		return -EFAULT; | 
 | 	} | 
 |  | 
 | 	err = dpsw_acl_remove_entry(ethsw->mc_io, 0, ethsw->dpsw_handle, | 
 | 				    block->acl_id, acl_entry_cfg); | 
 |  | 
 | 	dma_unmap_single(dev, acl_entry_cfg->key_iova, sizeof(cmd_buff), | 
 | 			 DMA_TO_DEVICE); | 
 | 	if (err) { | 
 | 		dev_err(dev, "dpsw_acl_remove_entry() failed %d\n", err); | 
 | 		return err; | 
 | 	} | 
 |  | 
 | 	kfree(cmd_buff); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int | 
 | dpaa2_switch_acl_entry_add_to_list(struct dpaa2_switch_filter_block *block, | 
 | 				   struct dpaa2_switch_acl_entry *entry) | 
 | { | 
 | 	struct dpaa2_switch_acl_entry *tmp; | 
 | 	struct list_head *pos, *n; | 
 | 	int index = 0; | 
 |  | 
 | 	if (list_empty(&block->acl_entries)) { | 
 | 		list_add(&entry->list, &block->acl_entries); | 
 | 		return index; | 
 | 	} | 
 |  | 
 | 	list_for_each_safe(pos, n, &block->acl_entries) { | 
 | 		tmp = list_entry(pos, struct dpaa2_switch_acl_entry, list); | 
 | 		if (entry->prio < tmp->prio) | 
 | 			break; | 
 | 		index++; | 
 | 	} | 
 | 	list_add(&entry->list, pos->prev); | 
 | 	return index; | 
 | } | 
 |  | 
 | static struct dpaa2_switch_acl_entry* | 
 | dpaa2_switch_acl_entry_get_by_index(struct dpaa2_switch_filter_block *block, | 
 | 				    int index) | 
 | { | 
 | 	struct dpaa2_switch_acl_entry *tmp; | 
 | 	int i = 0; | 
 |  | 
 | 	list_for_each_entry(tmp, &block->acl_entries, list) { | 
 | 		if (i == index) | 
 | 			return tmp; | 
 | 		++i; | 
 | 	} | 
 |  | 
 | 	return NULL; | 
 | } | 
 |  | 
 | static int | 
 | dpaa2_switch_acl_entry_set_precedence(struct dpaa2_switch_filter_block *block, | 
 | 				      struct dpaa2_switch_acl_entry *entry, | 
 | 				      int precedence) | 
 | { | 
 | 	int err; | 
 |  | 
 | 	err = dpaa2_switch_acl_entry_remove(block, entry); | 
 | 	if (err) | 
 | 		return err; | 
 |  | 
 | 	entry->cfg.precedence = precedence; | 
 | 	return dpaa2_switch_acl_entry_add(block, entry); | 
 | } | 
 |  | 
 | static int | 
 | dpaa2_switch_acl_tbl_add_entry(struct dpaa2_switch_filter_block *block, | 
 | 			       struct dpaa2_switch_acl_entry *entry) | 
 | { | 
 | 	struct dpaa2_switch_acl_entry *tmp; | 
 | 	int index, i, precedence, err; | 
 |  | 
 | 	/* Add the new ACL entry to the linked list and get its index */ | 
 | 	index = dpaa2_switch_acl_entry_add_to_list(block, entry); | 
 |  | 
 | 	/* Move up in priority the ACL entries to make space | 
 | 	 * for the new filter. | 
 | 	 */ | 
 | 	precedence = DPAA2_ETHSW_PORT_MAX_ACL_ENTRIES - block->num_acl_rules - 1; | 
 | 	for (i = 0; i < index; i++) { | 
 | 		tmp = dpaa2_switch_acl_entry_get_by_index(block, i); | 
 |  | 
 | 		err = dpaa2_switch_acl_entry_set_precedence(block, tmp, | 
 | 							    precedence); | 
 | 		if (err) | 
 | 			return err; | 
 |  | 
 | 		precedence++; | 
 | 	} | 
 |  | 
 | 	/* Add the new entry to hardware */ | 
 | 	entry->cfg.precedence = precedence; | 
 | 	err = dpaa2_switch_acl_entry_add(block, entry); | 
 | 	block->num_acl_rules++; | 
 |  | 
 | 	return err; | 
 | } | 
 |  | 
 | static struct dpaa2_switch_acl_entry * | 
 | dpaa2_switch_acl_tbl_find_entry_by_cookie(struct dpaa2_switch_filter_block *block, | 
 | 					  unsigned long cookie) | 
 | { | 
 | 	struct dpaa2_switch_acl_entry *tmp, *n; | 
 |  | 
 | 	list_for_each_entry_safe(tmp, n, &block->acl_entries, list) { | 
 | 		if (tmp->cookie == cookie) | 
 | 			return tmp; | 
 | 	} | 
 | 	return NULL; | 
 | } | 
 |  | 
 | static int | 
 | dpaa2_switch_acl_entry_get_index(struct dpaa2_switch_filter_block *block, | 
 | 				 struct dpaa2_switch_acl_entry *entry) | 
 | { | 
 | 	struct dpaa2_switch_acl_entry *tmp, *n; | 
 | 	int index = 0; | 
 |  | 
 | 	list_for_each_entry_safe(tmp, n, &block->acl_entries, list) { | 
 | 		if (tmp->cookie == entry->cookie) | 
 | 			return index; | 
 | 		index++; | 
 | 	} | 
 | 	return -ENOENT; | 
 | } | 
 |  | 
 | static struct dpaa2_switch_mirror_entry * | 
 | dpaa2_switch_mirror_find_entry_by_cookie(struct dpaa2_switch_filter_block *block, | 
 | 					 unsigned long cookie) | 
 | { | 
 | 	struct dpaa2_switch_mirror_entry *tmp, *n; | 
 |  | 
 | 	list_for_each_entry_safe(tmp, n, &block->mirror_entries, list) { | 
 | 		if (tmp->cookie == cookie) | 
 | 			return tmp; | 
 | 	} | 
 | 	return NULL; | 
 | } | 
 |  | 
 | static int | 
 | dpaa2_switch_acl_tbl_remove_entry(struct dpaa2_switch_filter_block *block, | 
 | 				  struct dpaa2_switch_acl_entry *entry) | 
 | { | 
 | 	struct dpaa2_switch_acl_entry *tmp; | 
 | 	int index, i, precedence, err; | 
 |  | 
 | 	index = dpaa2_switch_acl_entry_get_index(block, entry); | 
 |  | 
 | 	/* Remove from hardware the ACL entry */ | 
 | 	err = dpaa2_switch_acl_entry_remove(block, entry); | 
 | 	if (err) | 
 | 		return err; | 
 |  | 
 | 	block->num_acl_rules--; | 
 |  | 
 | 	/* Remove it from the list also */ | 
 | 	list_del(&entry->list); | 
 |  | 
 | 	/* Move down in priority the entries over the deleted one */ | 
 | 	precedence = entry->cfg.precedence; | 
 | 	for (i = index - 1; i >= 0; i--) { | 
 | 		tmp = dpaa2_switch_acl_entry_get_by_index(block, i); | 
 | 		err = dpaa2_switch_acl_entry_set_precedence(block, tmp, | 
 | 							    precedence); | 
 | 		if (err) | 
 | 			return err; | 
 |  | 
 | 		precedence--; | 
 | 	} | 
 |  | 
 | 	kfree(entry); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int dpaa2_switch_tc_parse_action_acl(struct ethsw_core *ethsw, | 
 | 					    struct flow_action_entry *cls_act, | 
 | 					    struct dpsw_acl_result *dpsw_act, | 
 | 					    struct netlink_ext_ack *extack) | 
 | { | 
 | 	int err = 0; | 
 |  | 
 | 	switch (cls_act->id) { | 
 | 	case FLOW_ACTION_TRAP: | 
 | 		dpsw_act->action = DPSW_ACL_ACTION_REDIRECT_TO_CTRL_IF; | 
 | 		break; | 
 | 	case FLOW_ACTION_REDIRECT: | 
 | 		if (!dpaa2_switch_port_dev_check(cls_act->dev)) { | 
 | 			NL_SET_ERR_MSG_MOD(extack, | 
 | 					   "Destination not a DPAA2 switch port"); | 
 | 			return -EOPNOTSUPP; | 
 | 		} | 
 |  | 
 | 		dpsw_act->if_id = dpaa2_switch_get_index(ethsw, cls_act->dev); | 
 | 		dpsw_act->action = DPSW_ACL_ACTION_REDIRECT; | 
 | 		break; | 
 | 	case FLOW_ACTION_DROP: | 
 | 		dpsw_act->action = DPSW_ACL_ACTION_DROP; | 
 | 		break; | 
 | 	default: | 
 | 		NL_SET_ERR_MSG_MOD(extack, | 
 | 				   "Action not supported"); | 
 | 		err = -EOPNOTSUPP; | 
 | 		goto out; | 
 | 	} | 
 |  | 
 | out: | 
 | 	return err; | 
 | } | 
 |  | 
 | static int | 
 | dpaa2_switch_block_add_mirror(struct dpaa2_switch_filter_block *block, | 
 | 			      struct dpaa2_switch_mirror_entry *entry, | 
 | 			      u16 to, struct netlink_ext_ack *extack) | 
 | { | 
 | 	unsigned long block_ports = block->ports; | 
 | 	struct ethsw_core *ethsw = block->ethsw; | 
 | 	struct ethsw_port_priv *port_priv; | 
 | 	unsigned long ports_added = 0; | 
 | 	u16 vlan = entry->cfg.vlan_id; | 
 | 	bool mirror_port_enabled; | 
 | 	int err, port; | 
 |  | 
 | 	/* Setup the mirroring port */ | 
 | 	mirror_port_enabled = (ethsw->mirror_port != ethsw->sw_attr.num_ifs); | 
 | 	if (!mirror_port_enabled) { | 
 | 		err = dpsw_set_reflection_if(ethsw->mc_io, 0, | 
 | 					     ethsw->dpsw_handle, to); | 
 | 		if (err) | 
 | 			return err; | 
 | 		ethsw->mirror_port = to; | 
 | 	} | 
 |  | 
 | 	/* Setup the same egress mirroring configuration on all the switch | 
 | 	 * ports that share the same filter block. | 
 | 	 */ | 
 | 	for_each_set_bit(port, &block_ports, ethsw->sw_attr.num_ifs) { | 
 | 		port_priv = ethsw->ports[port]; | 
 |  | 
 | 		/* We cannot add a per VLAN mirroring rule if the VLAN in | 
 | 		 * question is not installed on the switch port. | 
 | 		 */ | 
 | 		if (entry->cfg.filter == DPSW_REFLECTION_FILTER_INGRESS_VLAN && | 
 | 		    !(port_priv->vlans[vlan] & ETHSW_VLAN_MEMBER)) { | 
 | 			NL_SET_ERR_MSG(extack, | 
 | 				       "VLAN must be installed on the switch port"); | 
 | 			err = -EINVAL; | 
 | 			goto err_remove_filters; | 
 | 		} | 
 |  | 
 | 		err = dpsw_if_add_reflection(ethsw->mc_io, 0, | 
 | 					     ethsw->dpsw_handle, | 
 | 					     port, &entry->cfg); | 
 | 		if (err) | 
 | 			goto err_remove_filters; | 
 |  | 
 | 		ports_added |= BIT(port); | 
 | 	} | 
 |  | 
 | 	list_add(&entry->list, &block->mirror_entries); | 
 |  | 
 | 	return 0; | 
 |  | 
 | err_remove_filters: | 
 | 	for_each_set_bit(port, &ports_added, ethsw->sw_attr.num_ifs) { | 
 | 		dpsw_if_remove_reflection(ethsw->mc_io, 0, ethsw->dpsw_handle, | 
 | 					  port, &entry->cfg); | 
 | 	} | 
 |  | 
 | 	if (!mirror_port_enabled) | 
 | 		ethsw->mirror_port = ethsw->sw_attr.num_ifs; | 
 |  | 
 | 	return err; | 
 | } | 
 |  | 
 | static int | 
 | dpaa2_switch_block_remove_mirror(struct dpaa2_switch_filter_block *block, | 
 | 				 struct dpaa2_switch_mirror_entry *entry) | 
 | { | 
 | 	struct dpsw_reflection_cfg *cfg = &entry->cfg; | 
 | 	unsigned long block_ports = block->ports; | 
 | 	struct ethsw_core *ethsw = block->ethsw; | 
 | 	int port; | 
 |  | 
 | 	/* Remove this mirroring configuration from all the ports belonging to | 
 | 	 * the filter block. | 
 | 	 */ | 
 | 	for_each_set_bit(port, &block_ports, ethsw->sw_attr.num_ifs) | 
 | 		dpsw_if_remove_reflection(ethsw->mc_io, 0, ethsw->dpsw_handle, | 
 | 					  port, cfg); | 
 |  | 
 | 	/* Also remove it from the list of mirror filters */ | 
 | 	list_del(&entry->list); | 
 | 	kfree(entry); | 
 |  | 
 | 	/* If this was the last mirror filter, then unset the mirror port */ | 
 | 	if (list_empty(&block->mirror_entries)) | 
 | 		ethsw->mirror_port =  ethsw->sw_attr.num_ifs; | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int | 
 | dpaa2_switch_cls_flower_replace_acl(struct dpaa2_switch_filter_block *block, | 
 | 				    struct flow_cls_offload *cls) | 
 | { | 
 | 	struct flow_rule *rule = flow_cls_offload_flow_rule(cls); | 
 | 	struct netlink_ext_ack *extack = cls->common.extack; | 
 | 	struct dpaa2_switch_acl_entry *acl_entry; | 
 | 	struct ethsw_core *ethsw = block->ethsw; | 
 | 	struct flow_action_entry *act; | 
 | 	int err; | 
 |  | 
 | 	if (dpaa2_switch_acl_tbl_is_full(block)) { | 
 | 		NL_SET_ERR_MSG(extack, "Maximum filter capacity reached"); | 
 | 		return -ENOMEM; | 
 | 	} | 
 |  | 
 | 	acl_entry = kzalloc(sizeof(*acl_entry), GFP_KERNEL); | 
 | 	if (!acl_entry) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	err = dpaa2_switch_flower_parse_key(cls, &acl_entry->key); | 
 | 	if (err) | 
 | 		goto free_acl_entry; | 
 |  | 
 | 	act = &rule->action.entries[0]; | 
 | 	err = dpaa2_switch_tc_parse_action_acl(ethsw, act, | 
 | 					       &acl_entry->cfg.result, extack); | 
 | 	if (err) | 
 | 		goto free_acl_entry; | 
 |  | 
 | 	acl_entry->prio = cls->common.prio; | 
 | 	acl_entry->cookie = cls->cookie; | 
 |  | 
 | 	err = dpaa2_switch_acl_tbl_add_entry(block, acl_entry); | 
 | 	if (err) | 
 | 		goto free_acl_entry; | 
 |  | 
 | 	return 0; | 
 |  | 
 | free_acl_entry: | 
 | 	kfree(acl_entry); | 
 |  | 
 | 	return err; | 
 | } | 
 |  | 
 | static int dpaa2_switch_flower_parse_mirror_key(struct flow_cls_offload *cls, | 
 | 						u16 *vlan) | 
 | { | 
 | 	struct flow_rule *rule = flow_cls_offload_flow_rule(cls); | 
 | 	struct flow_dissector *dissector = rule->match.dissector; | 
 | 	struct netlink_ext_ack *extack = cls->common.extack; | 
 | 	int ret = -EOPNOTSUPP; | 
 |  | 
 | 	if (dissector->used_keys & | 
 | 	    ~(BIT(FLOW_DISSECTOR_KEY_BASIC) | | 
 | 	      BIT(FLOW_DISSECTOR_KEY_CONTROL) | | 
 | 	      BIT(FLOW_DISSECTOR_KEY_VLAN))) { | 
 | 		NL_SET_ERR_MSG_MOD(extack, | 
 | 				   "Mirroring is supported only per VLAN"); | 
 | 		return -EOPNOTSUPP; | 
 | 	} | 
 |  | 
 | 	if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_VLAN)) { | 
 | 		struct flow_match_vlan match; | 
 |  | 
 | 		flow_rule_match_vlan(rule, &match); | 
 |  | 
 | 		if (match.mask->vlan_priority != 0 || | 
 | 		    match.mask->vlan_dei != 0) { | 
 | 			NL_SET_ERR_MSG_MOD(extack, | 
 | 					   "Only matching on VLAN ID supported"); | 
 | 			return -EOPNOTSUPP; | 
 | 		} | 
 |  | 
 | 		if (match.mask->vlan_id != 0xFFF) { | 
 | 			NL_SET_ERR_MSG_MOD(extack, | 
 | 					   "Masked matching not supported"); | 
 | 			return -EOPNOTSUPP; | 
 | 		} | 
 |  | 
 | 		*vlan = (u16)match.key->vlan_id; | 
 | 		ret = 0; | 
 | 	} | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | static int | 
 | dpaa2_switch_cls_flower_replace_mirror(struct dpaa2_switch_filter_block *block, | 
 | 				       struct flow_cls_offload *cls) | 
 | { | 
 | 	struct netlink_ext_ack *extack = cls->common.extack; | 
 | 	struct dpaa2_switch_mirror_entry *mirror_entry; | 
 | 	struct ethsw_core *ethsw = block->ethsw; | 
 | 	struct dpaa2_switch_mirror_entry *tmp; | 
 | 	struct flow_action_entry *cls_act; | 
 | 	struct list_head *pos, *n; | 
 | 	bool mirror_port_enabled; | 
 | 	u16 if_id, vlan; | 
 | 	int err; | 
 |  | 
 | 	mirror_port_enabled = (ethsw->mirror_port != ethsw->sw_attr.num_ifs); | 
 | 	cls_act = &cls->rule->action.entries[0]; | 
 |  | 
 | 	/* Offload rules only when the destination is a DPAA2 switch port */ | 
 | 	if (!dpaa2_switch_port_dev_check(cls_act->dev)) { | 
 | 		NL_SET_ERR_MSG_MOD(extack, | 
 | 				   "Destination not a DPAA2 switch port"); | 
 | 		return -EOPNOTSUPP; | 
 | 	} | 
 | 	if_id = dpaa2_switch_get_index(ethsw, cls_act->dev); | 
 |  | 
 | 	/* We have a single mirror port but can configure egress mirroring on | 
 | 	 * all the other switch ports. We need to allow mirroring rules only | 
 | 	 * when the destination port is the same. | 
 | 	 */ | 
 | 	if (mirror_port_enabled && ethsw->mirror_port != if_id) { | 
 | 		NL_SET_ERR_MSG_MOD(extack, | 
 | 				   "Multiple mirror ports not supported"); | 
 | 		return -EBUSY; | 
 | 	} | 
 |  | 
 | 	/* Parse the key */ | 
 | 	err = dpaa2_switch_flower_parse_mirror_key(cls, &vlan); | 
 | 	if (err) | 
 | 		return err; | 
 |  | 
 | 	/* Make sure that we don't already have a mirror rule with the same | 
 | 	 * configuration. | 
 | 	 */ | 
 | 	list_for_each_safe(pos, n, &block->mirror_entries) { | 
 | 		tmp = list_entry(pos, struct dpaa2_switch_mirror_entry, list); | 
 |  | 
 | 		if (tmp->cfg.filter == DPSW_REFLECTION_FILTER_INGRESS_VLAN && | 
 | 		    tmp->cfg.vlan_id == vlan) { | 
 | 			NL_SET_ERR_MSG_MOD(extack, | 
 | 					   "VLAN mirror filter already installed"); | 
 | 			return -EBUSY; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	mirror_entry = kzalloc(sizeof(*mirror_entry), GFP_KERNEL); | 
 | 	if (!mirror_entry) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	mirror_entry->cfg.filter = DPSW_REFLECTION_FILTER_INGRESS_VLAN; | 
 | 	mirror_entry->cfg.vlan_id = vlan; | 
 | 	mirror_entry->cookie = cls->cookie; | 
 |  | 
 | 	return dpaa2_switch_block_add_mirror(block, mirror_entry, if_id, | 
 | 					     extack); | 
 | } | 
 |  | 
 | int dpaa2_switch_cls_flower_replace(struct dpaa2_switch_filter_block *block, | 
 | 				    struct flow_cls_offload *cls) | 
 | { | 
 | 	struct flow_rule *rule = flow_cls_offload_flow_rule(cls); | 
 | 	struct netlink_ext_ack *extack = cls->common.extack; | 
 | 	struct flow_action_entry *act; | 
 |  | 
 | 	if (!flow_offload_has_one_action(&rule->action)) { | 
 | 		NL_SET_ERR_MSG(extack, "Only singular actions are supported"); | 
 | 		return -EOPNOTSUPP; | 
 | 	} | 
 |  | 
 | 	act = &rule->action.entries[0]; | 
 | 	switch (act->id) { | 
 | 	case FLOW_ACTION_REDIRECT: | 
 | 	case FLOW_ACTION_TRAP: | 
 | 	case FLOW_ACTION_DROP: | 
 | 		return dpaa2_switch_cls_flower_replace_acl(block, cls); | 
 | 	case FLOW_ACTION_MIRRED: | 
 | 		return dpaa2_switch_cls_flower_replace_mirror(block, cls); | 
 | 	default: | 
 | 		NL_SET_ERR_MSG_MOD(extack, "Action not supported"); | 
 | 		return -EOPNOTSUPP; | 
 | 	} | 
 | } | 
 |  | 
 | int dpaa2_switch_cls_flower_destroy(struct dpaa2_switch_filter_block *block, | 
 | 				    struct flow_cls_offload *cls) | 
 | { | 
 | 	struct dpaa2_switch_mirror_entry *mirror_entry; | 
 | 	struct dpaa2_switch_acl_entry *acl_entry; | 
 |  | 
 | 	/* If this filter is a an ACL one, remove it */ | 
 | 	acl_entry = dpaa2_switch_acl_tbl_find_entry_by_cookie(block, | 
 | 							      cls->cookie); | 
 | 	if (acl_entry) | 
 | 		return dpaa2_switch_acl_tbl_remove_entry(block, acl_entry); | 
 |  | 
 | 	/* If not, then it has to be a mirror */ | 
 | 	mirror_entry = dpaa2_switch_mirror_find_entry_by_cookie(block, | 
 | 								cls->cookie); | 
 | 	if (mirror_entry) | 
 | 		return dpaa2_switch_block_remove_mirror(block, | 
 | 							mirror_entry); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int | 
 | dpaa2_switch_cls_matchall_replace_acl(struct dpaa2_switch_filter_block *block, | 
 | 				      struct tc_cls_matchall_offload *cls) | 
 | { | 
 | 	struct netlink_ext_ack *extack = cls->common.extack; | 
 | 	struct ethsw_core *ethsw = block->ethsw; | 
 | 	struct dpaa2_switch_acl_entry *acl_entry; | 
 | 	struct flow_action_entry *act; | 
 | 	int err; | 
 |  | 
 | 	if (dpaa2_switch_acl_tbl_is_full(block)) { | 
 | 		NL_SET_ERR_MSG(extack, "Maximum filter capacity reached"); | 
 | 		return -ENOMEM; | 
 | 	} | 
 |  | 
 | 	acl_entry = kzalloc(sizeof(*acl_entry), GFP_KERNEL); | 
 | 	if (!acl_entry) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	act = &cls->rule->action.entries[0]; | 
 | 	err = dpaa2_switch_tc_parse_action_acl(ethsw, act, | 
 | 					       &acl_entry->cfg.result, extack); | 
 | 	if (err) | 
 | 		goto free_acl_entry; | 
 |  | 
 | 	acl_entry->prio = cls->common.prio; | 
 | 	acl_entry->cookie = cls->cookie; | 
 |  | 
 | 	err = dpaa2_switch_acl_tbl_add_entry(block, acl_entry); | 
 | 	if (err) | 
 | 		goto free_acl_entry; | 
 |  | 
 | 	return 0; | 
 |  | 
 | free_acl_entry: | 
 | 	kfree(acl_entry); | 
 |  | 
 | 	return err; | 
 | } | 
 |  | 
 | static int | 
 | dpaa2_switch_cls_matchall_replace_mirror(struct dpaa2_switch_filter_block *block, | 
 | 					 struct tc_cls_matchall_offload *cls) | 
 | { | 
 | 	struct netlink_ext_ack *extack = cls->common.extack; | 
 | 	struct dpaa2_switch_mirror_entry *mirror_entry; | 
 | 	struct ethsw_core *ethsw = block->ethsw; | 
 | 	struct dpaa2_switch_mirror_entry *tmp; | 
 | 	struct flow_action_entry *cls_act; | 
 | 	struct list_head *pos, *n; | 
 | 	bool mirror_port_enabled; | 
 | 	u16 if_id; | 
 |  | 
 | 	mirror_port_enabled = (ethsw->mirror_port != ethsw->sw_attr.num_ifs); | 
 | 	cls_act = &cls->rule->action.entries[0]; | 
 |  | 
 | 	/* Offload rules only when the destination is a DPAA2 switch port */ | 
 | 	if (!dpaa2_switch_port_dev_check(cls_act->dev)) { | 
 | 		NL_SET_ERR_MSG_MOD(extack, | 
 | 				   "Destination not a DPAA2 switch port"); | 
 | 		return -EOPNOTSUPP; | 
 | 	} | 
 | 	if_id = dpaa2_switch_get_index(ethsw, cls_act->dev); | 
 |  | 
 | 	/* We have a single mirror port but can configure egress mirroring on | 
 | 	 * all the other switch ports. We need to allow mirroring rules only | 
 | 	 * when the destination port is the same. | 
 | 	 */ | 
 | 	if (mirror_port_enabled && ethsw->mirror_port != if_id) { | 
 | 		NL_SET_ERR_MSG_MOD(extack, | 
 | 				   "Multiple mirror ports not supported"); | 
 | 		return -EBUSY; | 
 | 	} | 
 |  | 
 | 	/* Make sure that we don't already have a mirror rule with the same | 
 | 	 * configuration. One matchall rule per block is the maximum. | 
 | 	 */ | 
 | 	list_for_each_safe(pos, n, &block->mirror_entries) { | 
 | 		tmp = list_entry(pos, struct dpaa2_switch_mirror_entry, list); | 
 |  | 
 | 		if (tmp->cfg.filter == DPSW_REFLECTION_FILTER_INGRESS_ALL) { | 
 | 			NL_SET_ERR_MSG_MOD(extack, | 
 | 					   "Matchall mirror filter already installed"); | 
 | 			return -EBUSY; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	mirror_entry = kzalloc(sizeof(*mirror_entry), GFP_KERNEL); | 
 | 	if (!mirror_entry) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	mirror_entry->cfg.filter = DPSW_REFLECTION_FILTER_INGRESS_ALL; | 
 | 	mirror_entry->cookie = cls->cookie; | 
 |  | 
 | 	return dpaa2_switch_block_add_mirror(block, mirror_entry, if_id, | 
 | 					     extack); | 
 | } | 
 |  | 
 | int dpaa2_switch_cls_matchall_replace(struct dpaa2_switch_filter_block *block, | 
 | 				      struct tc_cls_matchall_offload *cls) | 
 | { | 
 | 	struct netlink_ext_ack *extack = cls->common.extack; | 
 | 	struct flow_action_entry *act; | 
 |  | 
 | 	if (!flow_offload_has_one_action(&cls->rule->action)) { | 
 | 		NL_SET_ERR_MSG(extack, "Only singular actions are supported"); | 
 | 		return -EOPNOTSUPP; | 
 | 	} | 
 |  | 
 | 	act = &cls->rule->action.entries[0]; | 
 | 	switch (act->id) { | 
 | 	case FLOW_ACTION_REDIRECT: | 
 | 	case FLOW_ACTION_TRAP: | 
 | 	case FLOW_ACTION_DROP: | 
 | 		return dpaa2_switch_cls_matchall_replace_acl(block, cls); | 
 | 	case FLOW_ACTION_MIRRED: | 
 | 		return dpaa2_switch_cls_matchall_replace_mirror(block, cls); | 
 | 	default: | 
 | 		NL_SET_ERR_MSG_MOD(extack, "Action not supported"); | 
 | 		return -EOPNOTSUPP; | 
 | 	} | 
 | } | 
 |  | 
 | int dpaa2_switch_block_offload_mirror(struct dpaa2_switch_filter_block *block, | 
 | 				      struct ethsw_port_priv *port_priv) | 
 | { | 
 | 	struct ethsw_core *ethsw = port_priv->ethsw_data; | 
 | 	struct dpaa2_switch_mirror_entry *tmp; | 
 | 	int err; | 
 |  | 
 | 	list_for_each_entry(tmp, &block->mirror_entries, list) { | 
 | 		err = dpsw_if_add_reflection(ethsw->mc_io, 0, | 
 | 					     ethsw->dpsw_handle, | 
 | 					     port_priv->idx, &tmp->cfg); | 
 | 		if (err) | 
 | 			goto unwind_add; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 |  | 
 | unwind_add: | 
 | 	list_for_each_entry(tmp, &block->mirror_entries, list) | 
 | 		dpsw_if_remove_reflection(ethsw->mc_io, 0, | 
 | 					  ethsw->dpsw_handle, | 
 | 					  port_priv->idx, &tmp->cfg); | 
 |  | 
 | 	return err; | 
 | } | 
 |  | 
 | int dpaa2_switch_block_unoffload_mirror(struct dpaa2_switch_filter_block *block, | 
 | 					struct ethsw_port_priv *port_priv) | 
 | { | 
 | 	struct ethsw_core *ethsw = port_priv->ethsw_data; | 
 | 	struct dpaa2_switch_mirror_entry *tmp; | 
 | 	int err; | 
 |  | 
 | 	list_for_each_entry(tmp, &block->mirror_entries, list) { | 
 | 		err = dpsw_if_remove_reflection(ethsw->mc_io, 0, | 
 | 						ethsw->dpsw_handle, | 
 | 						port_priv->idx, &tmp->cfg); | 
 | 		if (err) | 
 | 			goto unwind_remove; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 |  | 
 | unwind_remove: | 
 | 	list_for_each_entry(tmp, &block->mirror_entries, list) | 
 | 		dpsw_if_add_reflection(ethsw->mc_io, 0, ethsw->dpsw_handle, | 
 | 				       port_priv->idx, &tmp->cfg); | 
 |  | 
 | 	return err; | 
 | } | 
 |  | 
 | int dpaa2_switch_cls_matchall_destroy(struct dpaa2_switch_filter_block *block, | 
 | 				      struct tc_cls_matchall_offload *cls) | 
 | { | 
 | 	struct dpaa2_switch_mirror_entry *mirror_entry; | 
 | 	struct dpaa2_switch_acl_entry *acl_entry; | 
 |  | 
 | 	/* If this filter is a an ACL one, remove it */ | 
 | 	acl_entry = dpaa2_switch_acl_tbl_find_entry_by_cookie(block, | 
 | 							      cls->cookie); | 
 | 	if (acl_entry) | 
 | 		return dpaa2_switch_acl_tbl_remove_entry(block, | 
 | 							 acl_entry); | 
 |  | 
 | 	/* If not, then it has to be a mirror */ | 
 | 	mirror_entry = dpaa2_switch_mirror_find_entry_by_cookie(block, | 
 | 								cls->cookie); | 
 | 	if (mirror_entry) | 
 | 		return dpaa2_switch_block_remove_mirror(block, | 
 | 							mirror_entry); | 
 |  | 
 | 	return 0; | 
 | } |