357 lines
8.8 KiB
C
357 lines
8.8 KiB
C
|
// SPDX-License-Identifier: (GPL-2.0 OR MIT)
|
||
|
/* Microsemi Ocelot Switch driver
|
||
|
* Copyright (c) 2019 Microsemi Corporation
|
||
|
*/
|
||
|
|
||
|
#include <net/pkt_cls.h>
|
||
|
#include <net/tc_act/tc_gact.h>
|
||
|
|
||
|
#include "ocelot_ace.h"
|
||
|
|
||
|
struct ocelot_port_block {
|
||
|
struct ocelot_acl_block *block;
|
||
|
struct ocelot_port *port;
|
||
|
};
|
||
|
|
||
|
static int ocelot_flower_parse_action(struct flow_cls_offload *f,
|
||
|
struct ocelot_ace_rule *rule)
|
||
|
{
|
||
|
const struct flow_action_entry *a;
|
||
|
int i;
|
||
|
|
||
|
if (f->rule->action.num_entries != 1)
|
||
|
return -EOPNOTSUPP;
|
||
|
|
||
|
flow_action_for_each(i, a, &f->rule->action) {
|
||
|
switch (a->id) {
|
||
|
case FLOW_ACTION_DROP:
|
||
|
rule->action = OCELOT_ACL_ACTION_DROP;
|
||
|
break;
|
||
|
case FLOW_ACTION_TRAP:
|
||
|
rule->action = OCELOT_ACL_ACTION_TRAP;
|
||
|
break;
|
||
|
default:
|
||
|
return -EOPNOTSUPP;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int ocelot_flower_parse(struct flow_cls_offload *f,
|
||
|
struct ocelot_ace_rule *ocelot_rule)
|
||
|
{
|
||
|
struct flow_rule *rule = flow_cls_offload_flow_rule(f);
|
||
|
struct flow_dissector *dissector = rule->match.dissector;
|
||
|
|
||
|
if (dissector->used_keys &
|
||
|
~(BIT(FLOW_DISSECTOR_KEY_CONTROL) |
|
||
|
BIT(FLOW_DISSECTOR_KEY_BASIC) |
|
||
|
BIT(FLOW_DISSECTOR_KEY_PORTS) |
|
||
|
BIT(FLOW_DISSECTOR_KEY_VLAN) |
|
||
|
BIT(FLOW_DISSECTOR_KEY_IPV4_ADDRS) |
|
||
|
BIT(FLOW_DISSECTOR_KEY_IPV6_ADDRS) |
|
||
|
BIT(FLOW_DISSECTOR_KEY_ETH_ADDRS))) {
|
||
|
return -EOPNOTSUPP;
|
||
|
}
|
||
|
|
||
|
if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_CONTROL)) {
|
||
|
struct flow_match_control match;
|
||
|
|
||
|
flow_rule_match_control(rule, &match);
|
||
|
}
|
||
|
|
||
|
if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_ETH_ADDRS)) {
|
||
|
struct flow_match_eth_addrs match;
|
||
|
u16 proto = ntohs(f->common.protocol);
|
||
|
|
||
|
/* The hw support mac matches only for MAC_ETYPE key,
|
||
|
* therefore if other matches(port, tcp flags, etc) are added
|
||
|
* then just bail out
|
||
|
*/
|
||
|
if ((dissector->used_keys &
|
||
|
(BIT(FLOW_DISSECTOR_KEY_ETH_ADDRS) |
|
||
|
BIT(FLOW_DISSECTOR_KEY_BASIC) |
|
||
|
BIT(FLOW_DISSECTOR_KEY_CONTROL))) !=
|
||
|
(BIT(FLOW_DISSECTOR_KEY_ETH_ADDRS) |
|
||
|
BIT(FLOW_DISSECTOR_KEY_BASIC) |
|
||
|
BIT(FLOW_DISSECTOR_KEY_CONTROL)))
|
||
|
return -EOPNOTSUPP;
|
||
|
|
||
|
if (proto == ETH_P_IP ||
|
||
|
proto == ETH_P_IPV6 ||
|
||
|
proto == ETH_P_ARP)
|
||
|
return -EOPNOTSUPP;
|
||
|
|
||
|
flow_rule_match_eth_addrs(rule, &match);
|
||
|
ocelot_rule->type = OCELOT_ACE_TYPE_ETYPE;
|
||
|
ether_addr_copy(ocelot_rule->frame.etype.dmac.value,
|
||
|
match.key->dst);
|
||
|
ether_addr_copy(ocelot_rule->frame.etype.smac.value,
|
||
|
match.key->src);
|
||
|
ether_addr_copy(ocelot_rule->frame.etype.dmac.mask,
|
||
|
match.mask->dst);
|
||
|
ether_addr_copy(ocelot_rule->frame.etype.smac.mask,
|
||
|
match.mask->src);
|
||
|
goto finished_key_parsing;
|
||
|
}
|
||
|
|
||
|
if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_BASIC)) {
|
||
|
struct flow_match_basic match;
|
||
|
|
||
|
flow_rule_match_basic(rule, &match);
|
||
|
if (ntohs(match.key->n_proto) == ETH_P_IP) {
|
||
|
ocelot_rule->type = OCELOT_ACE_TYPE_IPV4;
|
||
|
ocelot_rule->frame.ipv4.proto.value[0] =
|
||
|
match.key->ip_proto;
|
||
|
ocelot_rule->frame.ipv4.proto.mask[0] =
|
||
|
match.mask->ip_proto;
|
||
|
}
|
||
|
if (ntohs(match.key->n_proto) == ETH_P_IPV6) {
|
||
|
ocelot_rule->type = OCELOT_ACE_TYPE_IPV6;
|
||
|
ocelot_rule->frame.ipv6.proto.value[0] =
|
||
|
match.key->ip_proto;
|
||
|
ocelot_rule->frame.ipv6.proto.mask[0] =
|
||
|
match.mask->ip_proto;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_IPV4_ADDRS) &&
|
||
|
ntohs(f->common.protocol) == ETH_P_IP) {
|
||
|
struct flow_match_ipv4_addrs match;
|
||
|
u8 *tmp;
|
||
|
|
||
|
flow_rule_match_ipv4_addrs(rule, &match);
|
||
|
tmp = &ocelot_rule->frame.ipv4.sip.value.addr[0];
|
||
|
memcpy(tmp, &match.key->src, 4);
|
||
|
|
||
|
tmp = &ocelot_rule->frame.ipv4.sip.mask.addr[0];
|
||
|
memcpy(tmp, &match.mask->src, 4);
|
||
|
|
||
|
tmp = &ocelot_rule->frame.ipv4.dip.value.addr[0];
|
||
|
memcpy(tmp, &match.key->dst, 4);
|
||
|
|
||
|
tmp = &ocelot_rule->frame.ipv4.dip.mask.addr[0];
|
||
|
memcpy(tmp, &match.mask->dst, 4);
|
||
|
}
|
||
|
|
||
|
if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_IPV6_ADDRS) &&
|
||
|
ntohs(f->common.protocol) == ETH_P_IPV6) {
|
||
|
return -EOPNOTSUPP;
|
||
|
}
|
||
|
|
||
|
if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_PORTS)) {
|
||
|
struct flow_match_ports match;
|
||
|
|
||
|
flow_rule_match_ports(rule, &match);
|
||
|
ocelot_rule->frame.ipv4.sport.value = ntohs(match.key->src);
|
||
|
ocelot_rule->frame.ipv4.sport.mask = ntohs(match.mask->src);
|
||
|
ocelot_rule->frame.ipv4.dport.value = ntohs(match.key->dst);
|
||
|
ocelot_rule->frame.ipv4.dport.mask = ntohs(match.mask->dst);
|
||
|
}
|
||
|
|
||
|
if (flow_rule_match_key(rule, FLOW_DISSECTOR_KEY_VLAN)) {
|
||
|
struct flow_match_vlan match;
|
||
|
|
||
|
flow_rule_match_vlan(rule, &match);
|
||
|
ocelot_rule->type = OCELOT_ACE_TYPE_ANY;
|
||
|
ocelot_rule->vlan.vid.value = match.key->vlan_id;
|
||
|
ocelot_rule->vlan.vid.mask = match.mask->vlan_id;
|
||
|
ocelot_rule->vlan.pcp.value[0] = match.key->vlan_priority;
|
||
|
ocelot_rule->vlan.pcp.mask[0] = match.mask->vlan_priority;
|
||
|
}
|
||
|
|
||
|
finished_key_parsing:
|
||
|
ocelot_rule->prio = f->common.prio;
|
||
|
ocelot_rule->id = f->cookie;
|
||
|
return ocelot_flower_parse_action(f, ocelot_rule);
|
||
|
}
|
||
|
|
||
|
static
|
||
|
struct ocelot_ace_rule *ocelot_ace_rule_create(struct flow_cls_offload *f,
|
||
|
struct ocelot_port_block *block)
|
||
|
{
|
||
|
struct ocelot_ace_rule *rule;
|
||
|
|
||
|
rule = kzalloc(sizeof(*rule), GFP_KERNEL);
|
||
|
if (!rule)
|
||
|
return NULL;
|
||
|
|
||
|
rule->port = block->port;
|
||
|
rule->chip_port = block->port->chip_port;
|
||
|
return rule;
|
||
|
}
|
||
|
|
||
|
static int ocelot_flower_replace(struct flow_cls_offload *f,
|
||
|
struct ocelot_port_block *port_block)
|
||
|
{
|
||
|
struct ocelot_ace_rule *rule;
|
||
|
int ret;
|
||
|
|
||
|
rule = ocelot_ace_rule_create(f, port_block);
|
||
|
if (!rule)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
ret = ocelot_flower_parse(f, rule);
|
||
|
if (ret) {
|
||
|
kfree(rule);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
ret = ocelot_ace_rule_offload_add(rule);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
port_block->port->tc.offload_cnt++;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int ocelot_flower_destroy(struct flow_cls_offload *f,
|
||
|
struct ocelot_port_block *port_block)
|
||
|
{
|
||
|
struct ocelot_ace_rule rule;
|
||
|
int ret;
|
||
|
|
||
|
rule.prio = f->common.prio;
|
||
|
rule.port = port_block->port;
|
||
|
rule.id = f->cookie;
|
||
|
|
||
|
ret = ocelot_ace_rule_offload_del(&rule);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
port_block->port->tc.offload_cnt--;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int ocelot_flower_stats_update(struct flow_cls_offload *f,
|
||
|
struct ocelot_port_block *port_block)
|
||
|
{
|
||
|
struct ocelot_ace_rule rule;
|
||
|
int ret;
|
||
|
|
||
|
rule.prio = f->common.prio;
|
||
|
rule.port = port_block->port;
|
||
|
rule.id = f->cookie;
|
||
|
ret = ocelot_ace_rule_stats_update(&rule);
|
||
|
if (ret)
|
||
|
return ret;
|
||
|
|
||
|
flow_stats_update(&f->stats, 0x0, rule.stats.pkts, 0x0);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int ocelot_setup_tc_cls_flower(struct flow_cls_offload *f,
|
||
|
struct ocelot_port_block *port_block)
|
||
|
{
|
||
|
switch (f->command) {
|
||
|
case FLOW_CLS_REPLACE:
|
||
|
return ocelot_flower_replace(f, port_block);
|
||
|
case FLOW_CLS_DESTROY:
|
||
|
return ocelot_flower_destroy(f, port_block);
|
||
|
case FLOW_CLS_STATS:
|
||
|
return ocelot_flower_stats_update(f, port_block);
|
||
|
default:
|
||
|
return -EOPNOTSUPP;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int ocelot_setup_tc_block_cb_flower(enum tc_setup_type type,
|
||
|
void *type_data, void *cb_priv)
|
||
|
{
|
||
|
struct ocelot_port_block *port_block = cb_priv;
|
||
|
|
||
|
if (!tc_cls_can_offload_and_chain0(port_block->port->dev, type_data))
|
||
|
return -EOPNOTSUPP;
|
||
|
|
||
|
switch (type) {
|
||
|
case TC_SETUP_CLSFLOWER:
|
||
|
return ocelot_setup_tc_cls_flower(type_data, cb_priv);
|
||
|
case TC_SETUP_CLSMATCHALL:
|
||
|
return 0;
|
||
|
default:
|
||
|
return -EOPNOTSUPP;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static struct ocelot_port_block*
|
||
|
ocelot_port_block_create(struct ocelot_port *port)
|
||
|
{
|
||
|
struct ocelot_port_block *port_block;
|
||
|
|
||
|
port_block = kzalloc(sizeof(*port_block), GFP_KERNEL);
|
||
|
if (!port_block)
|
||
|
return NULL;
|
||
|
|
||
|
port_block->port = port;
|
||
|
|
||
|
return port_block;
|
||
|
}
|
||
|
|
||
|
static void ocelot_port_block_destroy(struct ocelot_port_block *block)
|
||
|
{
|
||
|
kfree(block);
|
||
|
}
|
||
|
|
||
|
static void ocelot_tc_block_unbind(void *cb_priv)
|
||
|
{
|
||
|
struct ocelot_port_block *port_block = cb_priv;
|
||
|
|
||
|
ocelot_port_block_destroy(port_block);
|
||
|
}
|
||
|
|
||
|
int ocelot_setup_tc_block_flower_bind(struct ocelot_port *port,
|
||
|
struct flow_block_offload *f)
|
||
|
{
|
||
|
struct ocelot_port_block *port_block;
|
||
|
struct flow_block_cb *block_cb;
|
||
|
int ret;
|
||
|
|
||
|
if (f->binder_type == FLOW_BLOCK_BINDER_TYPE_CLSACT_EGRESS)
|
||
|
return -EOPNOTSUPP;
|
||
|
|
||
|
block_cb = flow_block_cb_lookup(f->block,
|
||
|
ocelot_setup_tc_block_cb_flower, port);
|
||
|
if (!block_cb) {
|
||
|
port_block = ocelot_port_block_create(port);
|
||
|
if (!port_block)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
block_cb = flow_block_cb_alloc(ocelot_setup_tc_block_cb_flower,
|
||
|
port, port_block,
|
||
|
ocelot_tc_block_unbind);
|
||
|
if (IS_ERR(block_cb)) {
|
||
|
ret = PTR_ERR(block_cb);
|
||
|
goto err_cb_register;
|
||
|
}
|
||
|
flow_block_cb_add(block_cb, f);
|
||
|
list_add_tail(&block_cb->driver_list, f->driver_block_list);
|
||
|
} else {
|
||
|
port_block = flow_block_cb_priv(block_cb);
|
||
|
}
|
||
|
|
||
|
flow_block_cb_incref(block_cb);
|
||
|
return 0;
|
||
|
|
||
|
err_cb_register:
|
||
|
ocelot_port_block_destroy(port_block);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
void ocelot_setup_tc_block_flower_unbind(struct ocelot_port *port,
|
||
|
struct flow_block_offload *f)
|
||
|
{
|
||
|
struct flow_block_cb *block_cb;
|
||
|
|
||
|
block_cb = flow_block_cb_lookup(f->block,
|
||
|
ocelot_setup_tc_block_cb_flower, port);
|
||
|
if (!block_cb)
|
||
|
return;
|
||
|
|
||
|
if (!flow_block_cb_decref(block_cb)) {
|
||
|
flow_block_cb_remove(block_cb, f);
|
||
|
list_del(&block_cb->driver_list);
|
||
|
}
|
||
|
}
|