| // 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; | |
| } |