root/drivers/net/ethernet/sfc/tc_bindings.c
// SPDX-License-Identifier: GPL-2.0-only
/****************************************************************************
 * Driver for Solarflare network controllers and boards
 * Copyright 2022 Xilinx Inc.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 as published
 * by the Free Software Foundation, incorporated herein by reference.
 */

#include "tc_bindings.h"
#include "tc.h"
#include "tc_encap_actions.h"

struct efx_tc_block_binding {
        struct list_head list;
        struct efx_nic *efx;
        struct efx_rep *efv;
        struct net_device *otherdev; /* may actually be us */
        struct flow_block *block;
};

static struct efx_tc_block_binding *efx_tc_find_binding(struct efx_nic *efx,
                                                        struct net_device *otherdev)
{
        struct efx_tc_block_binding *binding;

        ASSERT_RTNL();
        list_for_each_entry(binding, &efx->tc->block_list, list)
                if (binding->otherdev == otherdev)
                        return binding;
        return NULL;
}

static int efx_tc_block_cb(enum tc_setup_type type, void *type_data,
                           void *cb_priv)
{
        struct efx_tc_block_binding *binding = cb_priv;
        struct flow_cls_offload *tcf = type_data;

        switch (type) {
        case TC_SETUP_CLSFLOWER:
                return efx_tc_flower(binding->efx, binding->otherdev,
                                     tcf, binding->efv);
        default:
                return -EOPNOTSUPP;
        }
}

void efx_tc_block_unbind(void *cb_priv)
{
        struct efx_tc_block_binding *binding = cb_priv;

        list_del(&binding->list);
        kfree(binding);
}

static struct efx_tc_block_binding *efx_tc_create_binding(
                        struct efx_nic *efx, struct efx_rep *efv,
                        struct net_device *otherdev, struct flow_block *block)
{
        struct efx_tc_block_binding *binding = kmalloc_obj(*binding);

        if (!binding)
                return ERR_PTR(-ENOMEM);
        binding->efx = efx;
        binding->efv = efv;
        binding->otherdev = otherdev;
        binding->block = block;
        list_add(&binding->list, &efx->tc->block_list);
        return binding;
}

int efx_tc_setup_block(struct net_device *net_dev, struct efx_nic *efx,
                       struct flow_block_offload *tcb, struct efx_rep *efv)
{
        struct efx_tc_block_binding *binding;
        struct flow_block_cb *block_cb;
        int rc;

        if (tcb->binder_type != FLOW_BLOCK_BINDER_TYPE_CLSACT_INGRESS)
                return -EOPNOTSUPP;

        if (WARN_ON(!efx->tc))
                return -ENETDOWN;

        switch (tcb->command) {
        case FLOW_BLOCK_BIND:
                binding = efx_tc_create_binding(efx, efv, net_dev, tcb->block);
                if (IS_ERR(binding))
                        return PTR_ERR(binding);
                block_cb = flow_block_cb_alloc(efx_tc_block_cb, binding,
                                               binding, efx_tc_block_unbind);
                rc = PTR_ERR_OR_ZERO(block_cb);
                netif_dbg(efx, drv, efx->net_dev,
                          "bind %sdirect block for device %s, rc %d\n",
                          net_dev == efx->net_dev ? "" :
                          efv ? "semi" : "in",
                          net_dev ? net_dev->name : NULL, rc);
                if (rc) {
                        list_del(&binding->list);
                        kfree(binding);
                } else {
                        flow_block_cb_add(block_cb, tcb);
                }
                return rc;
        case FLOW_BLOCK_UNBIND:
                binding = efx_tc_find_binding(efx, net_dev);
                if (binding) {
                        block_cb = flow_block_cb_lookup(tcb->block,
                                                        efx_tc_block_cb,
                                                        binding);
                        if (block_cb) {
                                flow_block_cb_remove(block_cb, tcb);
                                netif_dbg(efx, drv, efx->net_dev,
                                          "unbound %sdirect block for device %s\n",
                                          net_dev == efx->net_dev ? "" :
                                          binding->efv ? "semi" : "in",
                                          net_dev ? net_dev->name : NULL);
                                return 0;
                        }
                }
                /* If we're in driver teardown, then we expect to have
                 * already unbound all our blocks (we did it early while
                 * we still had MCDI to remove the filters), so getting
                 * unbind callbacks now isn't a problem.
                 */
                netif_cond_dbg(efx, drv, efx->net_dev,
                               !efx->tc->up, warn,
                               "%sdirect block unbind for device %s, was never bound\n",
                               net_dev == efx->net_dev ? "" : "in",
                               net_dev ? net_dev->name : NULL);
                return -ENOENT;
        default:
                return -EOPNOTSUPP;
        }
}

int efx_tc_indr_setup_cb(struct net_device *net_dev, struct Qdisc *sch,
                         void *cb_priv, enum tc_setup_type type,
                         void *type_data, void *data,
                         void (*cleanup)(struct flow_block_cb *block_cb))
{
        struct flow_block_offload *tcb = type_data;
        struct efx_tc_block_binding *binding;
        struct flow_block_cb *block_cb;
        struct efx_nic *efx = cb_priv;
        bool is_ovs_int_port;
        int rc;

        if (!net_dev)
                return -EOPNOTSUPP;

        if (tcb->binder_type != FLOW_BLOCK_BINDER_TYPE_CLSACT_INGRESS &&
            tcb->binder_type != FLOW_BLOCK_BINDER_TYPE_CLSACT_EGRESS)
                return -EOPNOTSUPP;

        is_ovs_int_port = netif_is_ovs_master(net_dev);
        if (tcb->binder_type == FLOW_BLOCK_BINDER_TYPE_CLSACT_EGRESS &&
            !is_ovs_int_port)
                return -EOPNOTSUPP;

        if (is_ovs_int_port)
                return -EOPNOTSUPP;

        switch (type) {
        case TC_SETUP_BLOCK:
                switch (tcb->command) {
                case FLOW_BLOCK_BIND:
                        binding = efx_tc_create_binding(efx, NULL, net_dev, tcb->block);
                        if (IS_ERR(binding))
                                return PTR_ERR(binding);
                        block_cb = flow_indr_block_cb_alloc(efx_tc_block_cb, binding,
                                                            binding, efx_tc_block_unbind,
                                                            tcb, net_dev, sch, data, binding,
                                                            cleanup);
                        rc = PTR_ERR_OR_ZERO(block_cb);
                        netif_dbg(efx, drv, efx->net_dev,
                                  "bind indr block for device %s, rc %d\n",
                                  net_dev ? net_dev->name : NULL, rc);
                        if (rc) {
                                list_del(&binding->list);
                                kfree(binding);
                        } else {
                                flow_block_cb_add(block_cb, tcb);
                        }
                        return rc;
                case FLOW_BLOCK_UNBIND:
                        binding = efx_tc_find_binding(efx, net_dev);
                        if (!binding)
                                return -ENOENT;
                        block_cb = flow_block_cb_lookup(tcb->block,
                                                        efx_tc_block_cb,
                                                        binding);
                        if (!block_cb)
                                return -ENOENT;
                        flow_indr_block_cb_remove(block_cb, tcb);
                        netif_dbg(efx, drv, efx->net_dev,
                                  "unbind indr block for device %s\n",
                                  net_dev ? net_dev->name : NULL);
                        return 0;
                default:
                        return -EOPNOTSUPP;
                }
        default:
                return -EOPNOTSUPP;
        }
}

/* .ndo_setup_tc implementation
 * Entry point for flower block and filter management.
 */
int efx_tc_setup(struct net_device *net_dev, enum tc_setup_type type,
                 void *type_data)
{
        struct efx_nic *efx = efx_netdev_priv(net_dev);

        if (efx->type->is_vf)
                return -EOPNOTSUPP;
        if (!efx->tc)
                return -EOPNOTSUPP;

        if (type == TC_SETUP_CLSFLOWER)
                return efx_tc_flower(efx, net_dev, type_data, NULL);
        if (type == TC_SETUP_BLOCK)
                return efx_tc_setup_block(net_dev, efx, type_data, NULL);

        return -EOPNOTSUPP;
}

int efx_tc_netdev_event(struct efx_nic *efx, unsigned long event,
                        struct net_device *net_dev)
{
        if (efx->type->is_vf)
                return NOTIFY_DONE;

        if (event == NETDEV_UNREGISTER)
                efx_tc_unregister_egdev(efx, net_dev);

        return NOTIFY_OK;
}