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