162306a36Sopenharmony_ci// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
262306a36Sopenharmony_ci/* Copyright (c) 2017-2020 Mellanox Technologies. All rights reserved */
362306a36Sopenharmony_ci
462306a36Sopenharmony_ci#include <linux/kernel.h>
562306a36Sopenharmony_ci#include <linux/slab.h>
662306a36Sopenharmony_ci#include <linux/errno.h>
762306a36Sopenharmony_ci#include <linux/list.h>
862306a36Sopenharmony_ci#include <net/net_namespace.h>
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#include "spectrum.h"
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_cistruct mlxsw_sp_flow_block *
1362306a36Sopenharmony_cimlxsw_sp_flow_block_create(struct mlxsw_sp *mlxsw_sp, struct net *net)
1462306a36Sopenharmony_ci{
1562306a36Sopenharmony_ci	struct mlxsw_sp_flow_block *block;
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci	block = kzalloc(sizeof(*block), GFP_KERNEL);
1862306a36Sopenharmony_ci	if (!block)
1962306a36Sopenharmony_ci		return NULL;
2062306a36Sopenharmony_ci	INIT_LIST_HEAD(&block->binding_list);
2162306a36Sopenharmony_ci	INIT_LIST_HEAD(&block->mall.list);
2262306a36Sopenharmony_ci	block->mlxsw_sp = mlxsw_sp;
2362306a36Sopenharmony_ci	block->net = net;
2462306a36Sopenharmony_ci	return block;
2562306a36Sopenharmony_ci}
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_civoid mlxsw_sp_flow_block_destroy(struct mlxsw_sp_flow_block *block)
2862306a36Sopenharmony_ci{
2962306a36Sopenharmony_ci	WARN_ON(!list_empty(&block->binding_list));
3062306a36Sopenharmony_ci	kfree(block);
3162306a36Sopenharmony_ci}
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_cistatic struct mlxsw_sp_flow_block_binding *
3462306a36Sopenharmony_cimlxsw_sp_flow_block_lookup(struct mlxsw_sp_flow_block *block,
3562306a36Sopenharmony_ci			   struct mlxsw_sp_port *mlxsw_sp_port, bool ingress)
3662306a36Sopenharmony_ci{
3762306a36Sopenharmony_ci	struct mlxsw_sp_flow_block_binding *binding;
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci	list_for_each_entry(binding, &block->binding_list, list)
4062306a36Sopenharmony_ci		if (binding->mlxsw_sp_port == mlxsw_sp_port &&
4162306a36Sopenharmony_ci		    binding->ingress == ingress)
4262306a36Sopenharmony_ci			return binding;
4362306a36Sopenharmony_ci	return NULL;
4462306a36Sopenharmony_ci}
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_cistatic bool
4762306a36Sopenharmony_cimlxsw_sp_flow_block_ruleset_bound(const struct mlxsw_sp_flow_block *block)
4862306a36Sopenharmony_ci{
4962306a36Sopenharmony_ci	return block->ruleset_zero;
5062306a36Sopenharmony_ci}
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_cistatic int mlxsw_sp_flow_block_bind(struct mlxsw_sp *mlxsw_sp,
5362306a36Sopenharmony_ci				    struct mlxsw_sp_flow_block *block,
5462306a36Sopenharmony_ci				    struct mlxsw_sp_port *mlxsw_sp_port,
5562306a36Sopenharmony_ci				    bool ingress,
5662306a36Sopenharmony_ci				    struct netlink_ext_ack *extack)
5762306a36Sopenharmony_ci{
5862306a36Sopenharmony_ci	struct mlxsw_sp_flow_block_binding *binding;
5962306a36Sopenharmony_ci	int err;
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	if (WARN_ON(mlxsw_sp_flow_block_lookup(block, mlxsw_sp_port, ingress)))
6262306a36Sopenharmony_ci		return -EEXIST;
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	if (ingress && block->ingress_blocker_rule_count) {
6562306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "Block cannot be bound to ingress because it contains unsupported rules");
6662306a36Sopenharmony_ci		return -EOPNOTSUPP;
6762306a36Sopenharmony_ci	}
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	if (!ingress && block->egress_blocker_rule_count) {
7062306a36Sopenharmony_ci		NL_SET_ERR_MSG_MOD(extack, "Block cannot be bound to egress because it contains unsupported rules");
7162306a36Sopenharmony_ci		return -EOPNOTSUPP;
7262306a36Sopenharmony_ci	}
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci	err = mlxsw_sp_mall_port_bind(block, mlxsw_sp_port, extack);
7562306a36Sopenharmony_ci	if (err)
7662306a36Sopenharmony_ci		return err;
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci	binding = kzalloc(sizeof(*binding), GFP_KERNEL);
7962306a36Sopenharmony_ci	if (!binding) {
8062306a36Sopenharmony_ci		err = -ENOMEM;
8162306a36Sopenharmony_ci		goto err_binding_alloc;
8262306a36Sopenharmony_ci	}
8362306a36Sopenharmony_ci	binding->mlxsw_sp_port = mlxsw_sp_port;
8462306a36Sopenharmony_ci	binding->ingress = ingress;
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	if (mlxsw_sp_flow_block_ruleset_bound(block)) {
8762306a36Sopenharmony_ci		err = mlxsw_sp_acl_ruleset_bind(mlxsw_sp, block, binding);
8862306a36Sopenharmony_ci		if (err)
8962306a36Sopenharmony_ci			goto err_ruleset_bind;
9062306a36Sopenharmony_ci	}
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	if (ingress)
9362306a36Sopenharmony_ci		block->ingress_binding_count++;
9462306a36Sopenharmony_ci	else
9562306a36Sopenharmony_ci		block->egress_binding_count++;
9662306a36Sopenharmony_ci	list_add(&binding->list, &block->binding_list);
9762306a36Sopenharmony_ci	return 0;
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_cierr_ruleset_bind:
10062306a36Sopenharmony_ci	kfree(binding);
10162306a36Sopenharmony_cierr_binding_alloc:
10262306a36Sopenharmony_ci	mlxsw_sp_mall_port_unbind(block, mlxsw_sp_port);
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	return err;
10562306a36Sopenharmony_ci}
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_cistatic int mlxsw_sp_flow_block_unbind(struct mlxsw_sp *mlxsw_sp,
10862306a36Sopenharmony_ci				      struct mlxsw_sp_flow_block *block,
10962306a36Sopenharmony_ci				      struct mlxsw_sp_port *mlxsw_sp_port,
11062306a36Sopenharmony_ci				      bool ingress)
11162306a36Sopenharmony_ci{
11262306a36Sopenharmony_ci	struct mlxsw_sp_flow_block_binding *binding;
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	binding = mlxsw_sp_flow_block_lookup(block, mlxsw_sp_port, ingress);
11562306a36Sopenharmony_ci	if (!binding)
11662306a36Sopenharmony_ci		return -ENOENT;
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	list_del(&binding->list);
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	if (ingress)
12162306a36Sopenharmony_ci		block->ingress_binding_count--;
12262306a36Sopenharmony_ci	else
12362306a36Sopenharmony_ci		block->egress_binding_count--;
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	if (mlxsw_sp_flow_block_ruleset_bound(block))
12662306a36Sopenharmony_ci		mlxsw_sp_acl_ruleset_unbind(mlxsw_sp, block, binding);
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	kfree(binding);
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	mlxsw_sp_mall_port_unbind(block, mlxsw_sp_port);
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	return 0;
13362306a36Sopenharmony_ci}
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_cistatic int mlxsw_sp_flow_block_mall_cb(struct mlxsw_sp_flow_block *flow_block,
13662306a36Sopenharmony_ci				       struct tc_cls_matchall_offload *f)
13762306a36Sopenharmony_ci{
13862306a36Sopenharmony_ci	struct mlxsw_sp *mlxsw_sp = mlxsw_sp_flow_block_mlxsw_sp(flow_block);
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ci	switch (f->command) {
14162306a36Sopenharmony_ci	case TC_CLSMATCHALL_REPLACE:
14262306a36Sopenharmony_ci		return mlxsw_sp_mall_replace(mlxsw_sp, flow_block, f);
14362306a36Sopenharmony_ci	case TC_CLSMATCHALL_DESTROY:
14462306a36Sopenharmony_ci		mlxsw_sp_mall_destroy(flow_block, f);
14562306a36Sopenharmony_ci		return 0;
14662306a36Sopenharmony_ci	default:
14762306a36Sopenharmony_ci		return -EOPNOTSUPP;
14862306a36Sopenharmony_ci	}
14962306a36Sopenharmony_ci}
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_cistatic int mlxsw_sp_flow_block_flower_cb(struct mlxsw_sp_flow_block *flow_block,
15262306a36Sopenharmony_ci					 struct flow_cls_offload *f)
15362306a36Sopenharmony_ci{
15462306a36Sopenharmony_ci	struct mlxsw_sp *mlxsw_sp = mlxsw_sp_flow_block_mlxsw_sp(flow_block);
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	switch (f->command) {
15762306a36Sopenharmony_ci	case FLOW_CLS_REPLACE:
15862306a36Sopenharmony_ci		return mlxsw_sp_flower_replace(mlxsw_sp, flow_block, f);
15962306a36Sopenharmony_ci	case FLOW_CLS_DESTROY:
16062306a36Sopenharmony_ci		mlxsw_sp_flower_destroy(mlxsw_sp, flow_block, f);
16162306a36Sopenharmony_ci		return 0;
16262306a36Sopenharmony_ci	case FLOW_CLS_STATS:
16362306a36Sopenharmony_ci		return mlxsw_sp_flower_stats(mlxsw_sp, flow_block, f);
16462306a36Sopenharmony_ci	case FLOW_CLS_TMPLT_CREATE:
16562306a36Sopenharmony_ci		return mlxsw_sp_flower_tmplt_create(mlxsw_sp, flow_block, f);
16662306a36Sopenharmony_ci	case FLOW_CLS_TMPLT_DESTROY:
16762306a36Sopenharmony_ci		mlxsw_sp_flower_tmplt_destroy(mlxsw_sp, flow_block, f);
16862306a36Sopenharmony_ci		return 0;
16962306a36Sopenharmony_ci	default:
17062306a36Sopenharmony_ci		return -EOPNOTSUPP;
17162306a36Sopenharmony_ci	}
17262306a36Sopenharmony_ci}
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_cistatic int mlxsw_sp_flow_block_cb(enum tc_setup_type type,
17562306a36Sopenharmony_ci				  void *type_data, void *cb_priv)
17662306a36Sopenharmony_ci{
17762306a36Sopenharmony_ci	struct mlxsw_sp_flow_block *flow_block = cb_priv;
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci	if (mlxsw_sp_flow_block_disabled(flow_block))
18062306a36Sopenharmony_ci		return -EOPNOTSUPP;
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	switch (type) {
18362306a36Sopenharmony_ci	case TC_SETUP_CLSMATCHALL:
18462306a36Sopenharmony_ci		return mlxsw_sp_flow_block_mall_cb(flow_block, type_data);
18562306a36Sopenharmony_ci	case TC_SETUP_CLSFLOWER:
18662306a36Sopenharmony_ci		return mlxsw_sp_flow_block_flower_cb(flow_block, type_data);
18762306a36Sopenharmony_ci	default:
18862306a36Sopenharmony_ci		return -EOPNOTSUPP;
18962306a36Sopenharmony_ci	}
19062306a36Sopenharmony_ci}
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_cistatic void mlxsw_sp_tc_block_release(void *cb_priv)
19362306a36Sopenharmony_ci{
19462306a36Sopenharmony_ci	struct mlxsw_sp_flow_block *flow_block = cb_priv;
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci	mlxsw_sp_flow_block_destroy(flow_block);
19762306a36Sopenharmony_ci}
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_cistatic LIST_HEAD(mlxsw_sp_block_cb_list);
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_cistatic int mlxsw_sp_setup_tc_block_bind(struct mlxsw_sp_port *mlxsw_sp_port,
20262306a36Sopenharmony_ci					struct flow_block_offload *f,
20362306a36Sopenharmony_ci					bool ingress)
20462306a36Sopenharmony_ci{
20562306a36Sopenharmony_ci	struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
20662306a36Sopenharmony_ci	struct mlxsw_sp_flow_block *flow_block;
20762306a36Sopenharmony_ci	struct flow_block_cb *block_cb;
20862306a36Sopenharmony_ci	bool register_block = false;
20962306a36Sopenharmony_ci	int err;
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci	block_cb = flow_block_cb_lookup(f->block, mlxsw_sp_flow_block_cb,
21262306a36Sopenharmony_ci					mlxsw_sp);
21362306a36Sopenharmony_ci	if (!block_cb) {
21462306a36Sopenharmony_ci		flow_block = mlxsw_sp_flow_block_create(mlxsw_sp, f->net);
21562306a36Sopenharmony_ci		if (!flow_block)
21662306a36Sopenharmony_ci			return -ENOMEM;
21762306a36Sopenharmony_ci		block_cb = flow_block_cb_alloc(mlxsw_sp_flow_block_cb,
21862306a36Sopenharmony_ci					       mlxsw_sp, flow_block,
21962306a36Sopenharmony_ci					       mlxsw_sp_tc_block_release);
22062306a36Sopenharmony_ci		if (IS_ERR(block_cb)) {
22162306a36Sopenharmony_ci			mlxsw_sp_flow_block_destroy(flow_block);
22262306a36Sopenharmony_ci			return PTR_ERR(block_cb);
22362306a36Sopenharmony_ci		}
22462306a36Sopenharmony_ci		register_block = true;
22562306a36Sopenharmony_ci	} else {
22662306a36Sopenharmony_ci		flow_block = flow_block_cb_priv(block_cb);
22762306a36Sopenharmony_ci	}
22862306a36Sopenharmony_ci	flow_block_cb_incref(block_cb);
22962306a36Sopenharmony_ci	err = mlxsw_sp_flow_block_bind(mlxsw_sp, flow_block,
23062306a36Sopenharmony_ci				       mlxsw_sp_port, ingress, f->extack);
23162306a36Sopenharmony_ci	if (err)
23262306a36Sopenharmony_ci		goto err_block_bind;
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci	if (ingress)
23562306a36Sopenharmony_ci		mlxsw_sp_port->ing_flow_block = flow_block;
23662306a36Sopenharmony_ci	else
23762306a36Sopenharmony_ci		mlxsw_sp_port->eg_flow_block = flow_block;
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci	if (register_block) {
24062306a36Sopenharmony_ci		flow_block_cb_add(block_cb, f);
24162306a36Sopenharmony_ci		list_add_tail(&block_cb->driver_list, &mlxsw_sp_block_cb_list);
24262306a36Sopenharmony_ci	}
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci	return 0;
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_cierr_block_bind:
24762306a36Sopenharmony_ci	if (!flow_block_cb_decref(block_cb))
24862306a36Sopenharmony_ci		flow_block_cb_free(block_cb);
24962306a36Sopenharmony_ci	return err;
25062306a36Sopenharmony_ci}
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_cistatic void mlxsw_sp_setup_tc_block_unbind(struct mlxsw_sp_port *mlxsw_sp_port,
25362306a36Sopenharmony_ci					   struct flow_block_offload *f,
25462306a36Sopenharmony_ci					   bool ingress)
25562306a36Sopenharmony_ci{
25662306a36Sopenharmony_ci	struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
25762306a36Sopenharmony_ci	struct mlxsw_sp_flow_block *flow_block;
25862306a36Sopenharmony_ci	struct flow_block_cb *block_cb;
25962306a36Sopenharmony_ci	int err;
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_ci	block_cb = flow_block_cb_lookup(f->block, mlxsw_sp_flow_block_cb,
26262306a36Sopenharmony_ci					mlxsw_sp);
26362306a36Sopenharmony_ci	if (!block_cb)
26462306a36Sopenharmony_ci		return;
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci	if (ingress)
26762306a36Sopenharmony_ci		mlxsw_sp_port->ing_flow_block = NULL;
26862306a36Sopenharmony_ci	else
26962306a36Sopenharmony_ci		mlxsw_sp_port->eg_flow_block = NULL;
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_ci	flow_block = flow_block_cb_priv(block_cb);
27262306a36Sopenharmony_ci	err = mlxsw_sp_flow_block_unbind(mlxsw_sp, flow_block,
27362306a36Sopenharmony_ci					 mlxsw_sp_port, ingress);
27462306a36Sopenharmony_ci	if (!err && !flow_block_cb_decref(block_cb)) {
27562306a36Sopenharmony_ci		flow_block_cb_remove(block_cb, f);
27662306a36Sopenharmony_ci		list_del(&block_cb->driver_list);
27762306a36Sopenharmony_ci	}
27862306a36Sopenharmony_ci}
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ciint mlxsw_sp_setup_tc_block_clsact(struct mlxsw_sp_port *mlxsw_sp_port,
28162306a36Sopenharmony_ci				   struct flow_block_offload *f,
28262306a36Sopenharmony_ci				   bool ingress)
28362306a36Sopenharmony_ci{
28462306a36Sopenharmony_ci	f->driver_block_list = &mlxsw_sp_block_cb_list;
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ci	switch (f->command) {
28762306a36Sopenharmony_ci	case FLOW_BLOCK_BIND:
28862306a36Sopenharmony_ci		return mlxsw_sp_setup_tc_block_bind(mlxsw_sp_port, f, ingress);
28962306a36Sopenharmony_ci	case FLOW_BLOCK_UNBIND:
29062306a36Sopenharmony_ci		mlxsw_sp_setup_tc_block_unbind(mlxsw_sp_port, f, ingress);
29162306a36Sopenharmony_ci		return 0;
29262306a36Sopenharmony_ci	default:
29362306a36Sopenharmony_ci		return -EOPNOTSUPP;
29462306a36Sopenharmony_ci	}
29562306a36Sopenharmony_ci}
296