root/drivers/net/ethernet/marvell/prestera/prestera_matchall.c
// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
/* Copyright (c) 2019-2022 Marvell International Ltd. All rights reserved */

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

#include "prestera.h"
#include "prestera_hw.h"
#include "prestera_flow.h"
#include "prestera_flower.h"
#include "prestera_matchall.h"
#include "prestera_span.h"

static int prestera_mall_prio_check(struct prestera_flow_block *block,
                                    struct tc_cls_matchall_offload *f)
{
        u32 flower_prio_min;
        u32 flower_prio_max;
        int err;

        err = prestera_flower_prio_get(block, f->common.chain_index,
                                       &flower_prio_min, &flower_prio_max);
        if (err == -ENOENT)
                /* No flower filters installed on this chain. */
                return 0;

        if (err) {
                NL_SET_ERR_MSG(f->common.extack, "Failed to get flower priorities");
                return err;
        }

        if (f->common.prio <= flower_prio_max && !block->ingress) {
                NL_SET_ERR_MSG(f->common.extack, "Failed to add in front of existing flower rules");
                return -EOPNOTSUPP;
        }
        if (f->common.prio >= flower_prio_min && block->ingress) {
                NL_SET_ERR_MSG(f->common.extack, "Failed to add behind of existing flower rules");
                return -EOPNOTSUPP;
        }

        return 0;
}

int prestera_mall_prio_get(struct prestera_flow_block *block,
                           u32 *prio_min, u32 *prio_max)
{
        if (!block->mall.bound)
                return -ENOENT;

        *prio_min = block->mall.prio_min;
        *prio_max = block->mall.prio_max;
        return 0;
}

static void prestera_mall_prio_update(struct prestera_flow_block *block,
                                      struct tc_cls_matchall_offload *f)
{
        block->mall.prio_min = min(block->mall.prio_min, f->common.prio);
        block->mall.prio_max = max(block->mall.prio_max, f->common.prio);
}

int prestera_mall_replace(struct prestera_flow_block *block,
                          struct tc_cls_matchall_offload *f)
{
        struct prestera_flow_block_binding *binding;
        __be16 protocol = f->common.protocol;
        struct flow_action_entry *act;
        struct prestera_port *port;
        int err;

        if (!flow_offload_has_one_action(&f->rule->action)) {
                NL_SET_ERR_MSG(f->common.extack,
                               "Only singular actions are supported");
                return -EOPNOTSUPP;
        }

        act = &f->rule->action.entries[0];

        if (!prestera_netdev_check(act->dev)) {
                NL_SET_ERR_MSG(f->common.extack,
                               "Only Marvell Prestera port is supported");
                return -EINVAL;
        }
        if (!tc_cls_can_offload_and_chain0(act->dev, &f->common))
                return -EOPNOTSUPP;
        if (act->id != FLOW_ACTION_MIRRED)
                return -EOPNOTSUPP;
        if (protocol != htons(ETH_P_ALL))
                return -EOPNOTSUPP;

        err = prestera_mall_prio_check(block, f);
        if (err)
                return err;

        port = netdev_priv(act->dev);

        list_for_each_entry(binding, &block->binding_list, list) {
                err = prestera_span_rule_add(binding, port, block->ingress);
                if (err == -EEXIST)
                        return err;
                if (err)
                        goto rollback;
        }

        prestera_mall_prio_update(block, f);

        block->mall.bound = true;
        return 0;

rollback:
        list_for_each_entry_continue_reverse(binding,
                                             &block->binding_list, list)
                prestera_span_rule_del(binding, block->ingress);
        return err;
}

void prestera_mall_destroy(struct prestera_flow_block *block)
{
        struct prestera_flow_block_binding *binding;

        list_for_each_entry(binding, &block->binding_list, list)
                prestera_span_rule_del(binding, block->ingress);

        block->mall.prio_min = UINT_MAX;
        block->mall.prio_max = 0;
        block->mall.bound = false;
}