242 lines
6.4 KiB
C
242 lines
6.4 KiB
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(sizeof(*binding), GFP_KERNEL);
|
|
|
|
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;
|
|
}
|