/*
* This file is part of the Chelsio T4 Ethernet driver for Linux.
*
* Copyright (c) 2016 Chelsio Communications, Inc. All rights reserved.
*
* This software is available to you under a choice of one of two
* licenses. You may choose to be licensed under the terms of the GNU
* General Public License (GPL) Version 2, available from the file
* COPYING in the main directory of this source tree, or the
* OpenIB.org BSD license below:
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above
* copyright notice, this list of conditions and the following
* disclaimer.
*
* - Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <net/tc_act/tc_gact.h>
#include <net/tc_act/tc_mirred.h>
#include "cxgb4.h"
#include "cxgb4_filter.h"
#include "cxgb4_tc_u32_parse.h"
#include "cxgb4_tc_u32.h"
/* Fill ch_filter_specification with parsed match value/mask pair. */
static int fill_match_fields(struct adapter *adap,
struct ch_filter_specification *fs,
struct tc_cls_u32_offload *cls,
const struct cxgb4_match_field *entry,
bool next_header)
{
unsigned int i, j;
__be32 val, mask;
int off, err;
bool found;
for (i = 0; i < cls->knode.sel->nkeys; i++) {
off = cls->knode.sel->keys[i].off;
val = cls->knode.sel->keys[i].val;
mask = cls->knode.sel->keys[i].mask;
if (next_header) {
/* For next headers, parse only keys with offmask */
if (!cls->knode.sel->keys[i].offmask)
continue;
} else {
/* For the remaining, parse only keys without offmask */
if (cls->knode.sel->keys[i].offmask)
continue;
}
found = false;
for (j = 0; entry[j].val; j++) {
if (off == entry[j].off) {
found = true;
err = entry[j].val(fs, val, mask);
if (err)
return err;
break;
}
}
if (!found)
return -EINVAL;
}
return 0;
}
/* Fill ch_filter_specification with parsed action. */
static int fill_action_fields(struct adapter *adap,
struct ch_filter_specification *fs,
struct tc_cls_u32_offload *cls)
{
unsigned int num_actions = 0;
const struct tc_action *a;
struct tcf_exts *exts;
int i;
exts = cls->knode.exts;
if (!tcf_exts_has_actions(exts))
return -EINVAL;
tcf_exts_for_each_action(i, a, exts) {
/* Don't allow more than one action per rule. */
if (num_actions)
return -EINVAL;
/* Drop in hardware. */
if (is_tcf_gact_shot(a)) {
fs->action = FILTER_DROP;
num_actions++;
continue;
}
/* Re-direct to specified port in hardware. */
if (is_tcf_mirred_egress_redirect(a)) {
struct net_device *n_dev, *target_dev;
bool found = false;
unsigned int i;
target_dev = tcf_mirred_dev(a);
for_each_port(adap, i) {
n_dev = adap->port[i];
if (target_dev == n_dev) {
fs->action = FILTER_SWITCH;
fs->eport = i;
found = true;
break;
}
}
/* Interface doesn't belong to any port of
* the underlying hardware.
*/
if (!found)
return -EINVAL;
num_actions++;
continue;
}
/* Un-supported action. */
return -EINVAL;
}
return 0;
}
int cxgb4_config_knode(struct net_device *dev, struct tc_cls_u32_offload *cls)
{
const struct cxgb4_match_field *start, *link_start = NULL;
struct netlink_ext_ack *extack = cls->common.extack;
struct adapter *adapter = netdev2adap(dev);
__be16 protocol = cls->common.protocol;
struct ch_filter_specification fs;
struct cxgb4_tc_u32_table *t;
struct cxgb4_link *link;
u32 uhtid, link_uhtid;
bool is_ipv6 = false;
u8 inet_family;
int filter_id;
int ret;
if (!can_tc_u32_offload(dev))
return -EOPNOTSUPP;
if (protocol != htons(ETH_P_IP) && protocol != htons(ETH_P_IPV6))
return -EOPNOTSUPP;
inet_family = (protocol == htons(ETH_P_IPV6)) ? PF_INET6 : PF_INET;
/* Get a free filter entry TID, where we can insert this new
* rule. Only insert rule if its prio doesn't conflict with
* existing rules.
*/
filter_id = cxgb4_get_free_ftid(dev, inet_family, false,
TC_U32_NODE(cls->knode.handle));
if (filter_id < 0) {
NL_SET_ERR_MSG_MOD(extack,
"No free LETCAM index available");
return -ENOMEM;
}
t = adapter->tc_u32;
uhtid = TC_U32_USERHTID(cls->knode.handle);
link_uhtid = TC_U32_USERHTID(cls->knode.link_handle);
/* Ensure that uhtid is either root u32 (i.e. 0x800)
* or a a valid linked bucket.
*/
if (uhtid != 0x800 && uhtid >= t->size)
return -EINVAL;
/* Ensure link handle uhtid is sane, if specified. */
if (link_uhtid >= t->size)
return -EINVAL;
memset(&fs, 0, sizeof(fs));
if (filter_id < adapter->tids.nhpftids)
fs.prio = 1;
fs.tc_prio = cls->common.prio;
fs.tc_cookie = cls->knode.handle;
if (protocol == htons(ETH_P_IPV6)) {
start = cxgb4_ipv6_fields;
is_ipv6 = true;
} else {
start = cxgb4_ipv4_fields;
is_ipv6 = false;
}
if (uhtid != 0x800) {
/* Link must exist from root node before insertion. */
if (!t->table[uhtid - 1].link_handle)
return -EINVAL;
/* Link must have a valid supported next header. */
link_start = t->table[uhtid - 1].match_field;
if (!link_start)
return -EINVAL;
}
/* Parse links and record them for subsequent jumps to valid
* next headers.
*/
if (link_uhtid) {
const struct cxgb4_next_header *next;
bool found = false;
unsigned int i, j;
__be32 val, mask;
int off;
if (t->table[link_uhtid - 1].link_handle) {
dev_err(adapter->pdev_dev,
"Link handle exists for: 0x%x\n",
link_uhtid);
return -EINVAL;
}
next = is_ipv6 ? cxgb4_ipv6_jumps : cxgb4_ipv4_jumps;
/* Try to find matches that allow jumps to next header. */
for (i = 0; next[i].jump; i++) {
if (next[i].sel.offoff != cls->knode.sel->offoff ||
next[i].sel.offshift != cls->knode.sel->offshift ||
next[i].sel.offmask != cls->knode.sel->offmask ||
next[i].sel.off != cls->knode.sel->off)
continue;
/* Found a possible candidate. Find a key that
* matches the corresponding offset, value, and
* mask to jump to next header.
*/
for (j = 0; j < cls->knode.sel->nkeys; j++) {
off = cls->knode.sel->keys[j].off;
val = cls->knode.sel->keys[j].val;
mask = cls->knode.sel->keys[j].mask;
if (next[i].key.off == off &&
next[i].key.val == val &&
next[i].key.mask == mask) {
found = true;
break;
}
}
if (!found)
continue; /* Try next candidate. */
/* Candidate to jump to next header found.
* Translate all keys to internal specification
* and store them in jump table. This spec is copied
* later to set the actual filters.
*/
ret = fill_match_fields(adapter, &fs, cls,
start, false);
if (ret)
goto out;
link = &t->table[link_uhtid - 1];
link->match_field = next[i].jump;
link->link_handle = cls->knode.handle;
memcpy(&link->fs, &fs, sizeof(fs));
break;
}
/* No candidate found to jump to next header. */
if (!found)
return -EINVAL;
return 0;
}
/* Fill ch_filter_specification match fields to be shipped to hardware.
* Copy the linked spec (if any) first. And then update the spec as
* needed.
*/
if (uhtid != 0x800 && t->table[uhtid - 1].link_handle) {
/* Copy linked ch_filter_specification */
memcpy(&fs, &t->table[uhtid - 1].fs, sizeof(fs));
ret = fill_match_fields(adapter, &fs, cls,
link_start, true);
if (ret)
goto out;
}
ret = fill_match_fields(adapter, &fs, cls, start, false);
if (ret)
goto out;
/* Fill ch_filter_specification action fields to be shipped to
* hardware.
*/
ret = fill_action_fields(adapter, &fs, cls);
if (ret)
goto out;
/* The filter spec has been completely built from the info
* provided from u32. We now set some default fields in the
* spec for sanity.
*/
/* Match only packets coming from the ingress port where this
* filter will be created.
*/
fs.val.iport = netdev2pinfo(dev)->port_id;
fs.mask.iport = ~0;
/* Enable filter hit counts. */
fs.hitcnts = 1;
/* Set type of filter - IPv6 or IPv4 */
fs.type = is_ipv6 ? 1 : 0;
/* Set the filter */
ret = cxgb4_set_filter(dev, filter_id, &fs);
if (ret)
goto out;
/* If this is a linked bucket, then set the corresponding
* entry in the bitmap to mark it as belonging to this linked
* bucket.
*/
if (uhtid != 0x800 && t->table[uhtid - 1].link_handle)
set_bit(filter_id, t->table[uhtid - 1].tid_map);
out:
return ret;
}
int cxgb4_delete_knode(struct net_device *dev, struct tc_cls_u32_offload *cls)
{
struct adapter *adapter = netdev2adap(dev);
unsigned int filter_id, max_tids, i, j;
struct cxgb4_link *link = NULL;
struct cxgb4_tc_u32_table *t;
struct filter_entry *f;
bool found = false;
u32 handle, uhtid;
u8 nslots;
int ret;
if (!can_tc_u32_offload(dev))
return -EOPNOTSUPP;
/* Fetch the location to delete the filter. */
max_tids = adapter->tids.nhpftids + adapter->tids.nftids;
spin_lock_bh(&adapter->tids.ftid_lock);
filter_id = 0;
while (filter_id < max_tids) {
if (filter_id < adapter->tids.nhpftids) {
i = filter_id;
f = &adapter->tids.hpftid_tab[i];
if (f->valid && f->fs.tc_cookie == cls->knode.handle) {
found = true;
break;
}
i = find_next_bit(adapter->tids.hpftid_bmap,
adapter->tids.nhpftids, i + 1);
if (i >= adapter->tids.nhpftids) {
filter_id = adapter->tids.nhpftids;
continue;
}
filter_id = i;
} else {
i = filter_id - adapter->tids.nhpftids;
f = &adapter->tids.ftid_tab[i];
if (f->valid && f->fs.tc_cookie == cls->knode.handle) {
found = true;
break;
}
i = find_next_bit(adapter->tids.ftid_bmap,
adapter->tids.nftids, i + 1);
if (i >= adapter->tids.nftids)
break;
filter_id = i + adapter->tids.nhpftids;
}
nslots = 0;
if (f->fs.type) {
nslots++;
if (CHELSIO_CHIP_VERSION(adapter->params.chip) <
CHELSIO_T6)
nslots += 2;
}
filter_id += nslots;
}
spin_unlock_bh(&adapter->tids.ftid_lock);
if (!found)
return -ERANGE;
t = adapter->tc_u32;
handle = cls->knode.handle;
uhtid = TC_U32_USERHTID(cls->knode.handle);
/* Ensure that uhtid is either root u32 (i.e. 0x800)
* or a a valid linked bucket.
*/
if (uhtid != 0x800 && uhtid >= t->size)
return -EINVAL;
/* Delete the specified filter */
if (uhtid != 0x800) {
link = &t->table[uhtid - 1];
if (!link->link_handle)
return -EINVAL;
if (!test_bit(filter_id, link->tid_map))
return -EINVAL;
}
ret = cxgb4_del_filter(dev, filter_id, NULL);
if (ret)
goto out;
if (link)
clear_bit(filter_id, link->tid_map);
/* If a link is being deleted, then delete all filters
* associated with the link.
*/
for (i = 0; i < t->size; i++) {
link = &t->table[i];
if (link->link_handle == handle) {
for (j = 0; j < max_tids; j++) {
if (!test_bit(j, link->tid_map))
continue;
ret = __cxgb4_del_filter(dev, j, NULL, NULL);
if (ret)
goto out;
clear_bit(j, link->tid_map);
}
/* Clear the link state */
link->match_field = NULL;
link->link_handle = 0;
memset(&link->fs, 0, sizeof(link->fs));
break;
}
}
out:
return ret;
}
void cxgb4_cleanup_tc_u32(struct adapter *adap)
{
struct cxgb4_tc_u32_table *t;
unsigned int i;
if (!adap->tc_u32)
return;
/* Free up all allocated memory. */
t = adap->tc_u32;
for (i = 0; i < t->size; i++) {
struct cxgb4_link *link = &t->table[i];
kvfree(link->tid_map);
}
kvfree(adap->tc_u32);
}
struct cxgb4_tc_u32_table *cxgb4_init_tc_u32(struct adapter *adap)
{
unsigned int max_tids = adap->tids.nftids + adap->tids.nhpftids;
struct cxgb4_tc_u32_table *t;
unsigned int i;
if (!max_tids)
return NULL;
t = kvzalloc(struct_size(t, table, max_tids), GFP_KERNEL);
if (!t)
return NULL;
t->size = max_tids;
for (i = 0; i < t->size; i++) {
struct cxgb4_link *link = &t->table[i];
unsigned int bmap_size;
bmap_size = BITS_TO_LONGS(max_tids);
link->tid_map = kvcalloc(bmap_size, sizeof(unsigned long),
GFP_KERNEL);
if (!link->tid_map)
goto out_no_mem;
bitmap_zero(link->tid_map, max_tids);
}
return t;
out_no_mem:
for (i = 0; i < t->size; i++) {
struct cxgb4_link *link = &t->table[i];
kvfree(link->tid_map);
}
kvfree(t);
return NULL;
}