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