Commit 8b474a9f authored by Serhiy Boiko's avatar Serhiy Boiko Committed by David S. Miller
Browse files

net: marvell: Implement TC flower offload



Add ACL infrastructure for Prestera Switch ASICs family devices to
offload cls_flower rules to be processed in the HW.

ACL implementation is based on tc filter api. The flower classifier
is supported to configure ACL rules/matches/action.

Supported actions:

    - drop
    - trap
    - pass

Supported dissector keys:

    - indev
    - src_mac
    - dst_mac
    - src_ip
    - dst_ip
    - ip_proto
    - src_port
    - dst_port
    - vlan_id
    - vlan_ethtype
    - icmp type/code

Co-developed-by: default avatarVolodymyr Mytnyk <vmytnyk@marvell.com>
Signed-off-by: default avatarVolodymyr Mytnyk <vmytnyk@marvell.com>
Signed-off-by: default avatarSerhiy Boiko <serhiy.boiko@plvision.eu>
Signed-off-by: default avatarVadym Kochan <vkochan@marvell.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 220e898d
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -2,6 +2,7 @@
obj-$(CONFIG_PRESTERA)	+= prestera.o
prestera-objs		:= prestera_main.o prestera_hw.o prestera_dsa.o \
			   prestera_rxtx.o prestera_devlink.o prestera_ethtool.o \
			   prestera_switchdev.o
			   prestera_switchdev.o prestera_acl.o prestera_flow.o \
			   prestera_flower.o

obj-$(CONFIG_PRESTERA_PCI)	+= prestera_pci.o
+5 −0
Original line number Diff line number Diff line
@@ -67,9 +67,12 @@ struct prestera_lag {
	u16 lag_id;
};

struct prestera_flow_block;

struct prestera_port {
	struct net_device *dev;
	struct prestera_switch *sw;
	struct prestera_flow_block *flow_block;
	struct devlink_port dl_port;
	struct list_head lag_member;
	struct prestera_lag *lag;
@@ -171,11 +174,13 @@ struct prestera_event {
struct prestera_switchdev;
struct prestera_rxtx;
struct prestera_trap_data;
struct prestera_acl;

struct prestera_switch {
	struct prestera_device *dev;
	struct prestera_switchdev *swdev;
	struct prestera_rxtx *rxtx;
	struct prestera_acl *acl;
	struct list_head event_handlers;
	struct notifier_block netdev_nb;
	struct prestera_trap_data *trap_data;
+374 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
/* Copyright (c) 2020 Marvell International Ltd. All rights reserved */

#include <linux/rhashtable.h>

#include "prestera.h"
#include "prestera_hw.h"
#include "prestera_acl.h"

struct prestera_acl {
	struct prestera_switch *sw;
	struct list_head rules;
};

struct prestera_acl_ruleset {
	struct rhashtable rule_ht;
	struct prestera_switch *sw;
	u16 id;
};

struct prestera_acl_rule {
	struct rhash_head ht_node;
	struct list_head list;
	struct list_head match_list;
	struct list_head action_list;
	struct prestera_flow_block *block;
	unsigned long cookie;
	u32 priority;
	u8 n_actions;
	u8 n_matches;
	u32 id;
};

static const struct rhashtable_params prestera_acl_rule_ht_params = {
	.key_len = sizeof(unsigned long),
	.key_offset = offsetof(struct prestera_acl_rule, cookie),
	.head_offset = offsetof(struct prestera_acl_rule, ht_node),
	.automatic_shrinking = true,
};

static struct prestera_acl_ruleset *
prestera_acl_ruleset_create(struct prestera_switch *sw)
{
	struct prestera_acl_ruleset *ruleset;
	int err;

	ruleset = kzalloc(sizeof(*ruleset), GFP_KERNEL);
	if (!ruleset)
		return ERR_PTR(-ENOMEM);

	err = rhashtable_init(&ruleset->rule_ht, &prestera_acl_rule_ht_params);
	if (err)
		goto err_rhashtable_init;

	err = prestera_hw_acl_ruleset_create(sw, &ruleset->id);
	if (err)
		goto err_ruleset_create;

	ruleset->sw = sw;

	return ruleset;

err_ruleset_create:
	rhashtable_destroy(&ruleset->rule_ht);
err_rhashtable_init:
	kfree(ruleset);
	return ERR_PTR(err);
}

static void prestera_acl_ruleset_destroy(struct prestera_acl_ruleset *ruleset)
{
	prestera_hw_acl_ruleset_del(ruleset->sw, ruleset->id);
	rhashtable_destroy(&ruleset->rule_ht);
	kfree(ruleset);
}

struct prestera_flow_block *
prestera_acl_block_create(struct prestera_switch *sw, struct net *net)
{
	struct prestera_flow_block *block;

	block = kzalloc(sizeof(*block), GFP_KERNEL);
	if (!block)
		return NULL;
	INIT_LIST_HEAD(&block->binding_list);
	block->net = net;
	block->sw = sw;

	block->ruleset = prestera_acl_ruleset_create(sw);
	if (IS_ERR(block->ruleset)) {
		kfree(block);
		return NULL;
	}

	return block;
}

void prestera_acl_block_destroy(struct prestera_flow_block *block)
{
	prestera_acl_ruleset_destroy(block->ruleset);
	WARN_ON(!list_empty(&block->binding_list));
	kfree(block);
}

static struct prestera_flow_block_binding *
prestera_acl_block_lookup(struct prestera_flow_block *block,
			  struct prestera_port *port)
{
	struct prestera_flow_block_binding *binding;

	list_for_each_entry(binding, &block->binding_list, list)
		if (binding->port == port)
			return binding;

	return NULL;
}

int prestera_acl_block_bind(struct prestera_flow_block *block,
			    struct prestera_port *port)
{
	struct prestera_flow_block_binding *binding;
	int err;

	if (WARN_ON(prestera_acl_block_lookup(block, port)))
		return -EEXIST;

	binding = kzalloc(sizeof(*binding), GFP_KERNEL);
	if (!binding)
		return -ENOMEM;
	binding->port = port;

	err = prestera_hw_acl_port_bind(port, block->ruleset->id);
	if (err)
		goto err_rules_bind;

	list_add(&binding->list, &block->binding_list);
	return 0;

err_rules_bind:
	kfree(binding);
	return err;
}

int prestera_acl_block_unbind(struct prestera_flow_block *block,
			      struct prestera_port *port)
{
	struct prestera_flow_block_binding *binding;

	binding = prestera_acl_block_lookup(block, port);
	if (!binding)
		return -ENOENT;

	list_del(&binding->list);

	prestera_hw_acl_port_unbind(port, block->ruleset->id);

	kfree(binding);
	return 0;
}

struct prestera_acl_ruleset *
prestera_acl_block_ruleset_get(struct prestera_flow_block *block)
{
	return block->ruleset;
}

u16 prestera_acl_rule_ruleset_id_get(const struct prestera_acl_rule *rule)
{
	return rule->block->ruleset->id;
}

struct net *prestera_acl_block_net(struct prestera_flow_block *block)
{
	return block->net;
}

struct prestera_switch *prestera_acl_block_sw(struct prestera_flow_block *block)
{
	return block->sw;
}

struct prestera_acl_rule *
prestera_acl_rule_lookup(struct prestera_acl_ruleset *ruleset,
			 unsigned long cookie)
{
	return rhashtable_lookup_fast(&ruleset->rule_ht, &cookie,
				      prestera_acl_rule_ht_params);
}

struct prestera_acl_rule *
prestera_acl_rule_create(struct prestera_flow_block *block,
			 unsigned long cookie)
{
	struct prestera_acl_rule *rule;

	rule = kzalloc(sizeof(*rule), GFP_KERNEL);
	if (!rule)
		return ERR_PTR(-ENOMEM);

	INIT_LIST_HEAD(&rule->match_list);
	INIT_LIST_HEAD(&rule->action_list);
	rule->cookie = cookie;
	rule->block = block;

	return rule;
}

struct list_head *
prestera_acl_rule_match_list_get(struct prestera_acl_rule *rule)
{
	return &rule->match_list;
}

struct list_head *
prestera_acl_rule_action_list_get(struct prestera_acl_rule *rule)
{
	return &rule->action_list;
}

int prestera_acl_rule_action_add(struct prestera_acl_rule *rule,
				 struct prestera_acl_rule_action_entry *entry)
{
	struct prestera_acl_rule_action_entry *a_entry;

	a_entry = kmalloc(sizeof(*a_entry), GFP_KERNEL);
	if (!a_entry)
		return -ENOMEM;

	memcpy(a_entry, entry, sizeof(*entry));
	list_add(&a_entry->list, &rule->action_list);

	rule->n_actions++;
	return 0;
}

u8 prestera_acl_rule_action_len(struct prestera_acl_rule *rule)
{
	return rule->n_actions;
}

u32 prestera_acl_rule_priority_get(struct prestera_acl_rule *rule)
{
	return rule->priority;
}

void prestera_acl_rule_priority_set(struct prestera_acl_rule *rule,
				    u32 priority)
{
	rule->priority = priority;
}

int prestera_acl_rule_match_add(struct prestera_acl_rule *rule,
				struct prestera_acl_rule_match_entry *entry)
{
	struct prestera_acl_rule_match_entry *m_entry;

	m_entry = kmalloc(sizeof(*m_entry), GFP_KERNEL);
	if (!m_entry)
		return -ENOMEM;

	memcpy(m_entry, entry, sizeof(*entry));
	list_add(&m_entry->list, &rule->match_list);

	rule->n_matches++;
	return 0;
}

u8 prestera_acl_rule_match_len(struct prestera_acl_rule *rule)
{
	return rule->n_matches;
}

void prestera_acl_rule_destroy(struct prestera_acl_rule *rule)
{
	struct prestera_acl_rule_action_entry *a_entry;
	struct prestera_acl_rule_match_entry *m_entry;
	struct list_head *pos, *n;

	list_for_each_safe(pos, n, &rule->match_list) {
		m_entry = list_entry(pos, typeof(*m_entry), list);
		list_del(pos);
		kfree(m_entry);
	}

	list_for_each_safe(pos, n, &rule->action_list) {
		a_entry = list_entry(pos, typeof(*a_entry), list);
		list_del(pos);
		kfree(a_entry);
	}

	kfree(rule);
}

int prestera_acl_rule_add(struct prestera_switch *sw,
			  struct prestera_acl_rule *rule)
{
	u32 rule_id;
	int err;

	/* try to add rule to hash table first */
	err = rhashtable_insert_fast(&rule->block->ruleset->rule_ht,
				     &rule->ht_node,
				     prestera_acl_rule_ht_params);
	if (err)
		return err;

	/* add rule to hw */
	err = prestera_hw_acl_rule_add(sw, rule, &rule_id);
	if (err)
		goto err_rule_add;

	rule->id = rule_id;

	list_add_tail(&rule->list, &sw->acl->rules);

	return 0;

err_rule_add:
	rhashtable_remove_fast(&rule->block->ruleset->rule_ht, &rule->ht_node,
			       prestera_acl_rule_ht_params);
	return err;
}

void prestera_acl_rule_del(struct prestera_switch *sw,
			   struct prestera_acl_rule *rule)
{
	rhashtable_remove_fast(&rule->block->ruleset->rule_ht, &rule->ht_node,
			       prestera_acl_rule_ht_params);
	list_del(&rule->list);
	prestera_hw_acl_rule_del(sw, rule->id);
}

int prestera_acl_rule_get_stats(struct prestera_switch *sw,
				struct prestera_acl_rule *rule,
				u64 *packets, u64 *bytes, u64 *last_use)
{
	u64 current_packets;
	u64 current_bytes;
	int err;

	err = prestera_hw_acl_rule_stats_get(sw, rule->id, &current_packets,
					     &current_bytes);
	if (err)
		return err;

	*packets = current_packets;
	*bytes = current_bytes;
	*last_use = jiffies;

	return 0;
}

int prestera_acl_init(struct prestera_switch *sw)
{
	struct prestera_acl *acl;

	acl = kzalloc(sizeof(*acl), GFP_KERNEL);
	if (!acl)
		return -ENOMEM;

	INIT_LIST_HEAD(&acl->rules);
	sw->acl = acl;
	acl->sw = sw;

	return 0;
}

void prestera_acl_fini(struct prestera_switch *sw)
{
	struct prestera_acl *acl = sw->acl;

	WARN_ON(!list_empty(&acl->rules));
	kfree(acl);
}
+123 −0
Original line number Diff line number Diff line
/* SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 */
/* Copyright (c) 2020 Marvell International Ltd. All rights reserved. */

#ifndef _PRESTERA_ACL_H_
#define _PRESTERA_ACL_H_

enum prestera_acl_rule_match_entry_type {
	PRESTERA_ACL_RULE_MATCH_ENTRY_TYPE_ETH_TYPE = 1,
	PRESTERA_ACL_RULE_MATCH_ENTRY_TYPE_ETH_DMAC,
	PRESTERA_ACL_RULE_MATCH_ENTRY_TYPE_ETH_SMAC,
	PRESTERA_ACL_RULE_MATCH_ENTRY_TYPE_IP_PROTO,
	PRESTERA_ACL_RULE_MATCH_ENTRY_TYPE_PORT,
	PRESTERA_ACL_RULE_MATCH_ENTRY_TYPE_IP_SRC,
	PRESTERA_ACL_RULE_MATCH_ENTRY_TYPE_IP_DST,
	PRESTERA_ACL_RULE_MATCH_ENTRY_TYPE_L4_PORT_SRC,
	PRESTERA_ACL_RULE_MATCH_ENTRY_TYPE_L4_PORT_DST,
	PRESTERA_ACL_RULE_MATCH_ENTRY_TYPE_L4_PORT_RANGE_SRC,
	PRESTERA_ACL_RULE_MATCH_ENTRY_TYPE_L4_PORT_RANGE_DST,
	PRESTERA_ACL_RULE_MATCH_ENTRY_TYPE_VLAN_ID,
	PRESTERA_ACL_RULE_MATCH_ENTRY_TYPE_VLAN_TPID,
	PRESTERA_ACL_RULE_MATCH_ENTRY_TYPE_ICMP_TYPE,
	PRESTERA_ACL_RULE_MATCH_ENTRY_TYPE_ICMP_CODE
};

enum prestera_acl_rule_action {
	PRESTERA_ACL_RULE_ACTION_ACCEPT,
	PRESTERA_ACL_RULE_ACTION_DROP,
	PRESTERA_ACL_RULE_ACTION_TRAP
};

struct prestera_switch;
struct prestera_port;
struct prestera_acl_rule;
struct prestera_acl_ruleset;

struct prestera_flow_block_binding {
	struct list_head list;
	struct prestera_port *port;
};

struct prestera_flow_block {
	struct list_head binding_list;
	struct prestera_switch *sw;
	struct net *net;
	struct prestera_acl_ruleset *ruleset;
	struct flow_block_cb *block_cb;
};

struct prestera_acl_rule_action_entry {
	struct list_head list;
	enum prestera_acl_rule_action id;
};

struct prestera_acl_rule_match_entry {
	struct list_head list;
	enum prestera_acl_rule_match_entry_type type;
	union {
		struct {
			u8 key;
			u8 mask;
		} u8;
		struct {
			u16 key;
			u16 mask;
		} u16;
		struct {
			u32 key;
			u32 mask;
		} u32;
		struct {
			u64 key;
			u64 mask;
		} u64;
		struct {
			u8 key[ETH_ALEN];
			u8 mask[ETH_ALEN];
		} mac;
	} keymask;
};

int prestera_acl_init(struct prestera_switch *sw);
void prestera_acl_fini(struct prestera_switch *sw);
struct prestera_flow_block *
prestera_acl_block_create(struct prestera_switch *sw, struct net *net);
void prestera_acl_block_destroy(struct prestera_flow_block *block);
struct net *prestera_acl_block_net(struct prestera_flow_block *block);
struct prestera_switch *prestera_acl_block_sw(struct prestera_flow_block *block);
int prestera_acl_block_bind(struct prestera_flow_block *block,
			    struct prestera_port *port);
int prestera_acl_block_unbind(struct prestera_flow_block *block,
			      struct prestera_port *port);
struct prestera_acl_ruleset *
prestera_acl_block_ruleset_get(struct prestera_flow_block *block);
struct prestera_acl_rule *
prestera_acl_rule_create(struct prestera_flow_block *block,
			 unsigned long cookie);
u32 prestera_acl_rule_priority_get(struct prestera_acl_rule *rule);
void prestera_acl_rule_priority_set(struct prestera_acl_rule *rule,
				    u32 priority);
u16 prestera_acl_rule_ruleset_id_get(const struct prestera_acl_rule *rule);
struct list_head *
prestera_acl_rule_action_list_get(struct prestera_acl_rule *rule);
u8 prestera_acl_rule_action_len(struct prestera_acl_rule *rule);
u8 prestera_acl_rule_match_len(struct prestera_acl_rule *rule);
int prestera_acl_rule_action_add(struct prestera_acl_rule *rule,
				 struct prestera_acl_rule_action_entry *entry);
struct list_head *
prestera_acl_rule_match_list_get(struct prestera_acl_rule *rule);
int prestera_acl_rule_match_add(struct prestera_acl_rule *rule,
				struct prestera_acl_rule_match_entry *entry);
void prestera_acl_rule_destroy(struct prestera_acl_rule *rule);
struct prestera_acl_rule *
prestera_acl_rule_lookup(struct prestera_acl_ruleset *ruleset,
			 unsigned long cookie);
int prestera_acl_rule_add(struct prestera_switch *sw,
			  struct prestera_acl_rule *rule);
void prestera_acl_rule_del(struct prestera_switch *sw,
			   struct prestera_acl_rule *rule);
int prestera_acl_rule_get_stats(struct prestera_switch *sw,
				struct prestera_acl_rule *rule,
				u64 *packets, u64 *bytes, u64 *last_use);

#endif /* _PRESTERA_ACL_H_ */
+175 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
/* Copyright (c) 2020 Marvell International Ltd. All rights reserved */

#include <linux/kernel.h>
#include <linux/list.h>

#include "prestera.h"
#include "prestera_acl.h"
#include "prestera_flow.h"
#include "prestera_flower.h"

static LIST_HEAD(prestera_block_cb_list);

static int prestera_flow_block_flower_cb(struct prestera_flow_block *block,
					 struct flow_cls_offload *f)
{
	if (f->common.chain_index != 0)
		return -EOPNOTSUPP;

	switch (f->command) {
	case FLOW_CLS_REPLACE:
		return prestera_flower_replace(block, f);
	case FLOW_CLS_DESTROY:
		prestera_flower_destroy(block, f);
		return 0;
	case FLOW_CLS_STATS:
		return prestera_flower_stats(block, f);
	default:
		return -EOPNOTSUPP;
	}
}

static int prestera_flow_block_cb(enum tc_setup_type type,
				  void *type_data, void *cb_priv)
{
	struct prestera_flow_block *block = cb_priv;

	switch (type) {
	case TC_SETUP_CLSFLOWER:
		return prestera_flow_block_flower_cb(block, type_data);
	default:
		return -EOPNOTSUPP;
	}
}

static void prestera_flow_block_release(void *cb_priv)
{
	struct prestera_flow_block *block = cb_priv;

	prestera_acl_block_destroy(block);
}

static struct prestera_flow_block *
prestera_flow_block_get(struct prestera_switch *sw,
			struct flow_block_offload *f,
			bool *register_block)
{
	struct prestera_flow_block *block;
	struct flow_block_cb *block_cb;

	block_cb = flow_block_cb_lookup(f->block,
					prestera_flow_block_cb, sw);
	if (!block_cb) {
		block = prestera_acl_block_create(sw, f->net);
		if (!block)
			return ERR_PTR(-ENOMEM);

		block_cb = flow_block_cb_alloc(prestera_flow_block_cb,
					       sw, block,
					       prestera_flow_block_release);
		if (IS_ERR(block_cb)) {
			prestera_acl_block_destroy(block);
			return ERR_CAST(block_cb);
		}

		block->block_cb = block_cb;
		*register_block = true;
	} else {
		block = flow_block_cb_priv(block_cb);
		*register_block = false;
	}

	flow_block_cb_incref(block_cb);

	return block;
}

static void prestera_flow_block_put(struct prestera_flow_block *block)
{
	struct flow_block_cb *block_cb = block->block_cb;

	if (flow_block_cb_decref(block_cb))
		return;

	flow_block_cb_free(block_cb);
	prestera_acl_block_destroy(block);
}

static int prestera_setup_flow_block_bind(struct prestera_port *port,
					  struct flow_block_offload *f)
{
	struct prestera_switch *sw = port->sw;
	struct prestera_flow_block *block;
	struct flow_block_cb *block_cb;
	bool register_block;
	int err;

	block = prestera_flow_block_get(sw, f, &register_block);
	if (IS_ERR(block))
		return PTR_ERR(block);

	block_cb = block->block_cb;

	err = prestera_acl_block_bind(block, port);
	if (err)
		goto err_block_bind;

	if (register_block) {
		flow_block_cb_add(block_cb, f);
		list_add_tail(&block_cb->driver_list, &prestera_block_cb_list);
	}

	port->flow_block = block;
	return 0;

err_block_bind:
	prestera_flow_block_put(block);

	return err;
}

static void prestera_setup_flow_block_unbind(struct prestera_port *port,
					     struct flow_block_offload *f)
{
	struct prestera_switch *sw = port->sw;
	struct prestera_flow_block *block;
	struct flow_block_cb *block_cb;
	int err;

	block_cb = flow_block_cb_lookup(f->block, prestera_flow_block_cb, sw);
	if (!block_cb)
		return;

	block = flow_block_cb_priv(block_cb);

	err = prestera_acl_block_unbind(block, port);
	if (err)
		goto error;

	if (!flow_block_cb_decref(block_cb)) {
		flow_block_cb_remove(block_cb, f);
		list_del(&block_cb->driver_list);
	}
error:
	port->flow_block = NULL;
}

int prestera_flow_block_setup(struct prestera_port *port,
			      struct flow_block_offload *f)
{
	if (f->binder_type != FLOW_BLOCK_BINDER_TYPE_CLSACT_INGRESS)
		return -EOPNOTSUPP;

	f->driver_block_list = &prestera_block_cb_list;

	switch (f->command) {
	case FLOW_BLOCK_BIND:
		return prestera_setup_flow_block_bind(port, f);
	case FLOW_BLOCK_UNBIND:
		prestera_setup_flow_block_unbind(port, f);
		return 0;
	default:
		return -EOPNOTSUPP;
	}
}
Loading