| // SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 |
| /* Copyright (c) 2017-2020 Mellanox Technologies. All rights reserved */ |
| |
| #include <linux/kernel.h> |
| #include <linux/slab.h> |
| #include <linux/errno.h> |
| #include <linux/list.h> |
| #include <net/net_namespace.h> |
| |
| #include "spectrum.h" |
| |
| struct mlxsw_sp_flow_block * |
| mlxsw_sp_flow_block_create(struct mlxsw_sp *mlxsw_sp, struct net *net) |
| { |
| struct mlxsw_sp_flow_block *block; |
| |
| block = kzalloc(sizeof(*block), GFP_KERNEL); |
| if (!block) |
| return NULL; |
| INIT_LIST_HEAD(&block->binding_list); |
| INIT_LIST_HEAD(&block->mall.list); |
| block->mlxsw_sp = mlxsw_sp; |
| block->net = net; |
| return block; |
| } |
| |
| void mlxsw_sp_flow_block_destroy(struct mlxsw_sp_flow_block *block) |
| { |
| WARN_ON(!list_empty(&block->binding_list)); |
| kfree(block); |
| } |
| |
| static struct mlxsw_sp_flow_block_binding * |
| mlxsw_sp_flow_block_lookup(struct mlxsw_sp_flow_block *block, |
| struct mlxsw_sp_port *mlxsw_sp_port, bool ingress) |
| { |
| struct mlxsw_sp_flow_block_binding *binding; |
| |
| list_for_each_entry(binding, &block->binding_list, list) |
| if (binding->mlxsw_sp_port == mlxsw_sp_port && |
| binding->ingress == ingress) |
| return binding; |
| return NULL; |
| } |
| |
| static bool |
| mlxsw_sp_flow_block_ruleset_bound(const struct mlxsw_sp_flow_block *block) |
| { |
| return block->ruleset_zero; |
| } |
| |
| static int mlxsw_sp_flow_block_bind(struct mlxsw_sp *mlxsw_sp, |
| struct mlxsw_sp_flow_block *block, |
| struct mlxsw_sp_port *mlxsw_sp_port, |
| bool ingress, |
| struct netlink_ext_ack *extack) |
| { |
| struct mlxsw_sp_flow_block_binding *binding; |
| int err; |
| |
| if (WARN_ON(mlxsw_sp_flow_block_lookup(block, mlxsw_sp_port, ingress))) |
| return -EEXIST; |
| |
| if (ingress && block->ingress_blocker_rule_count) { |
| NL_SET_ERR_MSG_MOD(extack, "Block cannot be bound to ingress because it contains unsupported rules"); |
| return -EOPNOTSUPP; |
| } |
| |
| if (!ingress && block->egress_blocker_rule_count) { |
| NL_SET_ERR_MSG_MOD(extack, "Block cannot be bound to egress because it contains unsupported rules"); |
| return -EOPNOTSUPP; |
| } |
| |
| err = mlxsw_sp_mall_port_bind(block, mlxsw_sp_port); |
| if (err) |
| return err; |
| |
| binding = kzalloc(sizeof(*binding), GFP_KERNEL); |
| if (!binding) { |
| err = -ENOMEM; |
| goto err_binding_alloc; |
| } |
| binding->mlxsw_sp_port = mlxsw_sp_port; |
| binding->ingress = ingress; |
| |
| if (mlxsw_sp_flow_block_ruleset_bound(block)) { |
| err = mlxsw_sp_acl_ruleset_bind(mlxsw_sp, block, binding); |
| if (err) |
| goto err_ruleset_bind; |
| } |
| |
| if (ingress) |
| block->ingress_binding_count++; |
| else |
| block->egress_binding_count++; |
| list_add(&binding->list, &block->binding_list); |
| return 0; |
| |
| err_ruleset_bind: |
| kfree(binding); |
| err_binding_alloc: |
| mlxsw_sp_mall_port_unbind(block, mlxsw_sp_port); |
| |
| return err; |
| } |
| |
| static int mlxsw_sp_flow_block_unbind(struct mlxsw_sp *mlxsw_sp, |
| struct mlxsw_sp_flow_block *block, |
| struct mlxsw_sp_port *mlxsw_sp_port, |
| bool ingress) |
| { |
| struct mlxsw_sp_flow_block_binding *binding; |
| |
| binding = mlxsw_sp_flow_block_lookup(block, mlxsw_sp_port, ingress); |
| if (!binding) |
| return -ENOENT; |
| |
| list_del(&binding->list); |
| |
| if (ingress) |
| block->ingress_binding_count--; |
| else |
| block->egress_binding_count--; |
| |
| if (mlxsw_sp_flow_block_ruleset_bound(block)) |
| mlxsw_sp_acl_ruleset_unbind(mlxsw_sp, block, binding); |
| |
| kfree(binding); |
| |
| mlxsw_sp_mall_port_unbind(block, mlxsw_sp_port); |
| |
| return 0; |
| } |
| |
| static int mlxsw_sp_flow_block_mall_cb(struct mlxsw_sp_flow_block *flow_block, |
| struct tc_cls_matchall_offload *f) |
| { |
| struct mlxsw_sp *mlxsw_sp = mlxsw_sp_flow_block_mlxsw_sp(flow_block); |
| |
| switch (f->command) { |
| case TC_CLSMATCHALL_REPLACE: |
| return mlxsw_sp_mall_replace(mlxsw_sp, flow_block, f); |
| case TC_CLSMATCHALL_DESTROY: |
| mlxsw_sp_mall_destroy(flow_block, f); |
| return 0; |
| default: |
| return -EOPNOTSUPP; |
| } |
| } |
| |
| static int mlxsw_sp_flow_block_flower_cb(struct mlxsw_sp_flow_block *flow_block, |
| struct flow_cls_offload *f) |
| { |
| struct mlxsw_sp *mlxsw_sp = mlxsw_sp_flow_block_mlxsw_sp(flow_block); |
| |
| switch (f->command) { |
| case FLOW_CLS_REPLACE: |
| return mlxsw_sp_flower_replace(mlxsw_sp, flow_block, f); |
| case FLOW_CLS_DESTROY: |
| mlxsw_sp_flower_destroy(mlxsw_sp, flow_block, f); |
| return 0; |
| case FLOW_CLS_STATS: |
| return mlxsw_sp_flower_stats(mlxsw_sp, flow_block, f); |
| case FLOW_CLS_TMPLT_CREATE: |
| return mlxsw_sp_flower_tmplt_create(mlxsw_sp, flow_block, f); |
| case FLOW_CLS_TMPLT_DESTROY: |
| mlxsw_sp_flower_tmplt_destroy(mlxsw_sp, flow_block, f); |
| return 0; |
| default: |
| return -EOPNOTSUPP; |
| } |
| } |
| |
| static int mlxsw_sp_flow_block_cb(enum tc_setup_type type, |
| void *type_data, void *cb_priv) |
| { |
| struct mlxsw_sp_flow_block *flow_block = cb_priv; |
| |
| if (mlxsw_sp_flow_block_disabled(flow_block)) |
| return -EOPNOTSUPP; |
| |
| switch (type) { |
| case TC_SETUP_CLSMATCHALL: |
| return mlxsw_sp_flow_block_mall_cb(flow_block, type_data); |
| case TC_SETUP_CLSFLOWER: |
| return mlxsw_sp_flow_block_flower_cb(flow_block, type_data); |
| default: |
| return -EOPNOTSUPP; |
| } |
| } |
| |
| static void mlxsw_sp_tc_block_release(void *cb_priv) |
| { |
| struct mlxsw_sp_flow_block *flow_block = cb_priv; |
| |
| mlxsw_sp_flow_block_destroy(flow_block); |
| } |
| |
| static LIST_HEAD(mlxsw_sp_block_cb_list); |
| |
| static int mlxsw_sp_setup_tc_block_bind(struct mlxsw_sp_port *mlxsw_sp_port, |
| struct flow_block_offload *f, |
| bool ingress) |
| { |
| struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp; |
| struct mlxsw_sp_flow_block *flow_block; |
| struct flow_block_cb *block_cb; |
| bool register_block = false; |
| int err; |
| |
| block_cb = flow_block_cb_lookup(f->block, mlxsw_sp_flow_block_cb, |
| mlxsw_sp); |
| if (!block_cb) { |
| flow_block = mlxsw_sp_flow_block_create(mlxsw_sp, f->net); |
| if (!flow_block) |
| return -ENOMEM; |
| block_cb = flow_block_cb_alloc(mlxsw_sp_flow_block_cb, |
| mlxsw_sp, flow_block, |
| mlxsw_sp_tc_block_release); |
| if (IS_ERR(block_cb)) { |
| mlxsw_sp_flow_block_destroy(flow_block); |
| return PTR_ERR(block_cb); |
| } |
| register_block = true; |
| } else { |
| flow_block = flow_block_cb_priv(block_cb); |
| } |
| flow_block_cb_incref(block_cb); |
| err = mlxsw_sp_flow_block_bind(mlxsw_sp, flow_block, |
| mlxsw_sp_port, ingress, f->extack); |
| if (err) |
| goto err_block_bind; |
| |
| if (ingress) |
| mlxsw_sp_port->ing_flow_block = flow_block; |
| else |
| mlxsw_sp_port->eg_flow_block = flow_block; |
| |
| if (register_block) { |
| flow_block_cb_add(block_cb, f); |
| list_add_tail(&block_cb->driver_list, &mlxsw_sp_block_cb_list); |
| } |
| |
| return 0; |
| |
| err_block_bind: |
| if (!flow_block_cb_decref(block_cb)) |
| flow_block_cb_free(block_cb); |
| return err; |
| } |
| |
| static void mlxsw_sp_setup_tc_block_unbind(struct mlxsw_sp_port *mlxsw_sp_port, |
| struct flow_block_offload *f, |
| bool ingress) |
| { |
| struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp; |
| struct mlxsw_sp_flow_block *flow_block; |
| struct flow_block_cb *block_cb; |
| int err; |
| |
| block_cb = flow_block_cb_lookup(f->block, mlxsw_sp_flow_block_cb, |
| mlxsw_sp); |
| if (!block_cb) |
| return; |
| |
| if (ingress) |
| mlxsw_sp_port->ing_flow_block = NULL; |
| else |
| mlxsw_sp_port->eg_flow_block = NULL; |
| |
| flow_block = flow_block_cb_priv(block_cb); |
| err = mlxsw_sp_flow_block_unbind(mlxsw_sp, flow_block, |
| mlxsw_sp_port, ingress); |
| if (!err && !flow_block_cb_decref(block_cb)) { |
| flow_block_cb_remove(block_cb, f); |
| list_del(&block_cb->driver_list); |
| } |
| } |
| |
| int mlxsw_sp_setup_tc_block_clsact(struct mlxsw_sp_port *mlxsw_sp_port, |
| struct flow_block_offload *f, |
| bool ingress) |
| { |
| f->driver_block_list = &mlxsw_sp_block_cb_list; |
| |
| switch (f->command) { |
| case FLOW_BLOCK_BIND: |
| return mlxsw_sp_setup_tc_block_bind(mlxsw_sp_port, f, ingress); |
| case FLOW_BLOCK_UNBIND: |
| mlxsw_sp_setup_tc_block_unbind(mlxsw_sp_port, f, ingress); |
| return 0; |
| default: |
| return -EOPNOTSUPP; |
| } |
| } |