| // SPDX-License-Identifier: (GPL-2.0 OR MIT) |
| /* Microsemi Ocelot Switch TC driver |
| * |
| * Copyright (c) 2019 Microsemi Corporation |
| */ |
| |
| #include "ocelot_tc.h" |
| #include "ocelot_police.h" |
| #include "ocelot_ace.h" |
| #include <net/pkt_cls.h> |
| |
| static int ocelot_setup_tc_cls_matchall(struct ocelot_port *port, |
| struct tc_cls_matchall_offload *f, |
| bool ingress) |
| { |
| struct netlink_ext_ack *extack = f->common.extack; |
| struct ocelot_policer pol = { 0 }; |
| struct flow_action_entry *action; |
| int err; |
| |
| netdev_dbg(port->dev, "%s: port %u command %d cookie %lu\n", |
| __func__, port->chip_port, f->command, f->cookie); |
| |
| if (!ingress) { |
| NL_SET_ERR_MSG_MOD(extack, "Only ingress is supported"); |
| return -EOPNOTSUPP; |
| } |
| |
| switch (f->command) { |
| case TC_CLSMATCHALL_REPLACE: |
| if (!flow_offload_has_one_action(&f->rule->action)) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "Only one action is supported"); |
| return -EOPNOTSUPP; |
| } |
| |
| if (port->tc.block_shared) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "Rate limit is not supported on shared blocks"); |
| return -EOPNOTSUPP; |
| } |
| |
| action = &f->rule->action.entries[0]; |
| |
| if (action->id != FLOW_ACTION_POLICE) { |
| NL_SET_ERR_MSG_MOD(extack, "Unsupported action"); |
| return -EOPNOTSUPP; |
| } |
| |
| if (port->tc.police_id && port->tc.police_id != f->cookie) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "Only one policer per port is supported\n"); |
| return -EEXIST; |
| } |
| |
| pol.rate = (u32)div_u64(action->police.rate_bytes_ps, 1000) * 8; |
| pol.burst = (u32)div_u64(action->police.rate_bytes_ps * |
| PSCHED_NS2TICKS(action->police.burst), |
| PSCHED_TICKS_PER_SEC); |
| |
| err = ocelot_port_policer_add(port, &pol); |
| if (err) { |
| NL_SET_ERR_MSG_MOD(extack, "Could not add policer\n"); |
| return err; |
| } |
| |
| port->tc.police_id = f->cookie; |
| port->tc.offload_cnt++; |
| return 0; |
| case TC_CLSMATCHALL_DESTROY: |
| if (port->tc.police_id != f->cookie) |
| return -ENOENT; |
| |
| err = ocelot_port_policer_del(port); |
| if (err) { |
| NL_SET_ERR_MSG_MOD(extack, |
| "Could not delete policer\n"); |
| return err; |
| } |
| port->tc.police_id = 0; |
| port->tc.offload_cnt--; |
| return 0; |
| case TC_CLSMATCHALL_STATS: /* fall through */ |
| default: |
| return -EOPNOTSUPP; |
| } |
| } |
| |
| static int ocelot_setup_tc_block_cb(enum tc_setup_type type, |
| void *type_data, |
| void *cb_priv, bool ingress) |
| { |
| struct ocelot_port *port = cb_priv; |
| |
| if (!tc_cls_can_offload_and_chain0(port->dev, type_data)) |
| return -EOPNOTSUPP; |
| |
| switch (type) { |
| case TC_SETUP_CLSMATCHALL: |
| netdev_dbg(port->dev, "tc_block_cb: TC_SETUP_CLSMATCHALL %s\n", |
| ingress ? "ingress" : "egress"); |
| |
| return ocelot_setup_tc_cls_matchall(port, type_data, ingress); |
| case TC_SETUP_CLSFLOWER: |
| return 0; |
| default: |
| netdev_dbg(port->dev, "tc_block_cb: type %d %s\n", |
| type, |
| ingress ? "ingress" : "egress"); |
| |
| return -EOPNOTSUPP; |
| } |
| } |
| |
| static int ocelot_setup_tc_block_cb_ig(enum tc_setup_type type, |
| void *type_data, |
| void *cb_priv) |
| { |
| return ocelot_setup_tc_block_cb(type, type_data, |
| cb_priv, true); |
| } |
| |
| static int ocelot_setup_tc_block_cb_eg(enum tc_setup_type type, |
| void *type_data, |
| void *cb_priv) |
| { |
| return ocelot_setup_tc_block_cb(type, type_data, |
| cb_priv, false); |
| } |
| |
| static LIST_HEAD(ocelot_block_cb_list); |
| |
| static int ocelot_setup_tc_block(struct ocelot_port *port, |
| struct flow_block_offload *f) |
| { |
| struct flow_block_cb *block_cb; |
| flow_setup_cb_t *cb; |
| int err; |
| |
| netdev_dbg(port->dev, "tc_block command %d, binder_type %d\n", |
| f->command, f->binder_type); |
| |
| if (f->binder_type == FLOW_BLOCK_BINDER_TYPE_CLSACT_INGRESS) { |
| cb = ocelot_setup_tc_block_cb_ig; |
| port->tc.block_shared = f->block_shared; |
| } else if (f->binder_type == FLOW_BLOCK_BINDER_TYPE_CLSACT_EGRESS) { |
| cb = ocelot_setup_tc_block_cb_eg; |
| } else { |
| return -EOPNOTSUPP; |
| } |
| |
| f->driver_block_list = &ocelot_block_cb_list; |
| |
| switch (f->command) { |
| case FLOW_BLOCK_BIND: |
| if (flow_block_cb_is_busy(cb, port, &ocelot_block_cb_list)) |
| return -EBUSY; |
| |
| block_cb = flow_block_cb_alloc(cb, port, port, NULL); |
| if (IS_ERR(block_cb)) |
| return PTR_ERR(block_cb); |
| |
| err = ocelot_setup_tc_block_flower_bind(port, f); |
| if (err < 0) { |
| flow_block_cb_free(block_cb); |
| return err; |
| } |
| flow_block_cb_add(block_cb, f); |
| list_add_tail(&block_cb->driver_list, f->driver_block_list); |
| return 0; |
| case FLOW_BLOCK_UNBIND: |
| block_cb = flow_block_cb_lookup(f->block, cb, port); |
| if (!block_cb) |
| return -ENOENT; |
| |
| ocelot_setup_tc_block_flower_unbind(port, f); |
| flow_block_cb_remove(block_cb, f); |
| list_del(&block_cb->driver_list); |
| return 0; |
| default: |
| return -EOPNOTSUPP; |
| } |
| } |
| |
| int ocelot_setup_tc(struct net_device *dev, enum tc_setup_type type, |
| void *type_data) |
| { |
| struct ocelot_port *port = netdev_priv(dev); |
| |
| switch (type) { |
| case TC_SETUP_BLOCK: |
| return ocelot_setup_tc_block(port, type_data); |
| default: |
| return -EOPNOTSUPP; |
| } |
| return 0; |
| } |