Commit e60df624 authored by David S. Miller's avatar David S. Miller

Merge branch 'mlxsw-Introduce-TC-Flower-offload-using-TCAM'

Jiri Pirko says:

====================
mlxsw: Introduce TC Flower offload using TCAM

This patchset introduces support for offloading TC cls_flower and actions
to Spectrum TCAM-base policy engine.

The patchset contains patches to allow work with flexible keys and actions
which are used in Spectrum TCAM.

It also contains in-driver infrastructure for offloading TC rules to TCAM HW.
The TCAM management code is simple and limited for now. It is going to be
extended as a follow-up work.

The last patch uses the previously introduced infra to allow to implement
cls_flower offloading. Initially, only limited set of match-keys and only
a drop and forward actions are supported.

As a dependency, this patchset introduces parman - priority array
area manager - as a library.

v1->v2:
- patch11:
  - use __set_bit and __test_and_clear_bit as suggested by DaveM
- patch16:
  - Added documentation to the API functions as suggested by Tom Herbert
- patch17:
  - use __set_bit and __clear_bit as suggested by DaveM
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 3d67576d 7aa0f5aa
......@@ -9382,6 +9382,14 @@ F: drivers/video/fbdev/sti*
F: drivers/video/console/sti*
F: drivers/video/logo/logo_parisc*
PARMAN
M: Jiri Pirko <jiri@mellanox.com>
L: netdev@vger.kernel.org
S: Supported
F: lib/parman.c
F: lib/test_parman.c
F: include/linux/parman.h
PC87360 HARDWARE MONITORING DRIVER
M: Jim Cromie <jim.cromie@gmail.com>
L: linux-hwmon@vger.kernel.org
......
......@@ -73,6 +73,7 @@ config MLXSW_SWITCHX2
config MLXSW_SPECTRUM
tristate "Mellanox Technologies Spectrum support"
depends on MLXSW_CORE && MLXSW_PCI && NET_SWITCHDEV && VLAN_8021Q
select PARMAN
default m
---help---
This driver supports Mellanox Technologies Spectrum Ethernet
......
obj-$(CONFIG_MLXSW_CORE) += mlxsw_core.o
mlxsw_core-objs := core.o
mlxsw_core-objs := core.o core_acl_flex_keys.o \
core_acl_flex_actions.o
mlxsw_core-$(CONFIG_MLXSW_CORE_HWMON) += core_hwmon.o
mlxsw_core-$(CONFIG_MLXSW_CORE_THERMAL) += core_thermal.o
obj-$(CONFIG_MLXSW_PCI) += mlxsw_pci.o
......@@ -13,7 +14,8 @@ mlxsw_switchx2-objs := switchx2.o
obj-$(CONFIG_MLXSW_SPECTRUM) += mlxsw_spectrum.o
mlxsw_spectrum-objs := spectrum.o spectrum_buffers.o \
spectrum_switchdev.o spectrum_router.o \
spectrum_kvdl.o
spectrum_kvdl.o spectrum_acl_tcam.o \
spectrum_acl.o spectrum_flower.o
mlxsw_spectrum-$(CONFIG_MLXSW_SPECTRUM_DCB) += spectrum_dcb.o
obj-$(CONFIG_MLXSW_MINIMAL) += mlxsw_minimal.o
mlxsw_minimal-objs := minimal.o
/*
* drivers/net/ethernet/mellanox/mlxsw/core_acl_flex_actions.c
* Copyright (c) 2017 Mellanox Technologies. All rights reserved.
* Copyright (c) 2017 Jiri Pirko <jiri@mellanox.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
* 3. Neither the names of the copyright holders nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* Alternatively, this software may be distributed under the terms of the
* GNU General Public License ("GPL") version 2 as published by the Free
* Software Foundation.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/rhashtable.h>
#include <linux/list.h>
#include "item.h"
#include "core_acl_flex_actions.h"
enum mlxsw_afa_set_type {
MLXSW_AFA_SET_TYPE_NEXT,
MLXSW_AFA_SET_TYPE_GOTO,
};
/* afa_set_type
* Type of the record at the end of the action set.
*/
MLXSW_ITEM32(afa, set, type, 0xA0, 28, 4);
/* afa_set_next_action_set_ptr
* A pointer to the next action set in the KVD Centralized database.
*/
MLXSW_ITEM32(afa, set, next_action_set_ptr, 0xA4, 0, 24);
/* afa_set_goto_g
* group - When set, the binding is of an ACL group. When cleared,
* the binding is of an ACL.
* Must be set to 1 for Spectrum.
*/
MLXSW_ITEM32(afa, set, goto_g, 0xA4, 29, 1);
enum mlxsw_afa_set_goto_binding_cmd {
/* continue go the next binding point */
MLXSW_AFA_SET_GOTO_BINDING_CMD_NONE,
/* jump to the next binding point no return */
MLXSW_AFA_SET_GOTO_BINDING_CMD_JUMP,
/* terminate the acl binding */
MLXSW_AFA_SET_GOTO_BINDING_CMD_TERM = 4,
};
/* afa_set_goto_binding_cmd */
MLXSW_ITEM32(afa, set, goto_binding_cmd, 0xA4, 24, 3);
/* afa_set_goto_next_binding
* ACL/ACL group identifier. If the g bit is set, this field should hold
* the acl_group_id, else it should hold the acl_id.
*/
MLXSW_ITEM32(afa, set, goto_next_binding, 0xA4, 0, 16);
/* afa_all_action_type
* Action Type.
*/
MLXSW_ITEM32(afa, all, action_type, 0x00, 24, 6);
struct mlxsw_afa {
unsigned int max_acts_per_set;
const struct mlxsw_afa_ops *ops;
void *ops_priv;
struct rhashtable set_ht;
struct rhashtable fwd_entry_ht;
};
#define MLXSW_AFA_SET_LEN 0xA8
struct mlxsw_afa_set_ht_key {
char enc_actions[MLXSW_AFA_SET_LEN]; /* Encoded set */
bool is_first;
};
/* Set structure holds one action set record. It contains up to three
* actions (depends on size of particular actions). The set is either
* put directly to a rule, or it is stored in KVD linear area.
* To prevent duplicate entries in KVD linear area, a hashtable is
* used to track sets that were previously inserted and may be shared.
*/
struct mlxsw_afa_set {
struct rhash_head ht_node;
struct mlxsw_afa_set_ht_key ht_key;
u32 kvdl_index;
bool shared; /* Inserted in hashtable (doesn't mean that
* kvdl_index is valid).
*/
unsigned int ref_count;
struct mlxsw_afa_set *next; /* Pointer to the next set. */
struct mlxsw_afa_set *prev; /* Pointer to the previous set,
* note that set may have multiple
* sets from multiple blocks
* pointing at it. This is only
* usable until commit.
*/
};
static const struct rhashtable_params mlxsw_afa_set_ht_params = {
.key_len = sizeof(struct mlxsw_afa_set_ht_key),
.key_offset = offsetof(struct mlxsw_afa_set, ht_key),
.head_offset = offsetof(struct mlxsw_afa_set, ht_node),
.automatic_shrinking = true,
};
struct mlxsw_afa_fwd_entry_ht_key {
u8 local_port;
};
struct mlxsw_afa_fwd_entry {
struct rhash_head ht_node;
struct mlxsw_afa_fwd_entry_ht_key ht_key;
u32 kvdl_index;
unsigned int ref_count;
};
static const struct rhashtable_params mlxsw_afa_fwd_entry_ht_params = {
.key_len = sizeof(struct mlxsw_afa_fwd_entry_ht_key),
.key_offset = offsetof(struct mlxsw_afa_fwd_entry, ht_key),
.head_offset = offsetof(struct mlxsw_afa_fwd_entry, ht_node),
.automatic_shrinking = true,
};
struct mlxsw_afa *mlxsw_afa_create(unsigned int max_acts_per_set,
const struct mlxsw_afa_ops *ops,
void *ops_priv)
{
struct mlxsw_afa *mlxsw_afa;
int err;
mlxsw_afa = kzalloc(sizeof(*mlxsw_afa), GFP_KERNEL);
if (!mlxsw_afa)
return ERR_PTR(-ENOMEM);
err = rhashtable_init(&mlxsw_afa->set_ht, &mlxsw_afa_set_ht_params);
if (err)
goto err_set_rhashtable_init;
err = rhashtable_init(&mlxsw_afa->fwd_entry_ht,
&mlxsw_afa_fwd_entry_ht_params);
if (err)
goto err_fwd_entry_rhashtable_init;
mlxsw_afa->max_acts_per_set = max_acts_per_set;
mlxsw_afa->ops = ops;
mlxsw_afa->ops_priv = ops_priv;
return mlxsw_afa;
err_fwd_entry_rhashtable_init:
rhashtable_destroy(&mlxsw_afa->set_ht);
err_set_rhashtable_init:
kfree(mlxsw_afa);
return ERR_PTR(err);
}
EXPORT_SYMBOL(mlxsw_afa_create);
void mlxsw_afa_destroy(struct mlxsw_afa *mlxsw_afa)
{
rhashtable_destroy(&mlxsw_afa->fwd_entry_ht);
rhashtable_destroy(&mlxsw_afa->set_ht);
kfree(mlxsw_afa);
}
EXPORT_SYMBOL(mlxsw_afa_destroy);
static void mlxsw_afa_set_goto_set(struct mlxsw_afa_set *set,
enum mlxsw_afa_set_goto_binding_cmd cmd,
u16 group_id)
{
char *actions = set->ht_key.enc_actions;
mlxsw_afa_set_type_set(actions, MLXSW_AFA_SET_TYPE_GOTO);
mlxsw_afa_set_goto_g_set(actions, true);
mlxsw_afa_set_goto_binding_cmd_set(actions, cmd);
mlxsw_afa_set_goto_next_binding_set(actions, group_id);
}
static void mlxsw_afa_set_next_set(struct mlxsw_afa_set *set,
u32 next_set_kvdl_index)
{
char *actions = set->ht_key.enc_actions;
mlxsw_afa_set_type_set(actions, MLXSW_AFA_SET_TYPE_NEXT);
mlxsw_afa_set_next_action_set_ptr_set(actions, next_set_kvdl_index);
}
static struct mlxsw_afa_set *mlxsw_afa_set_create(bool is_first)
{
struct mlxsw_afa_set *set;
set = kzalloc(sizeof(*set), GFP_KERNEL);
if (!set)
return NULL;
/* Need to initialize the set to pass by default */
mlxsw_afa_set_goto_set(set, MLXSW_AFA_SET_GOTO_BINDING_CMD_TERM, 0);
set->ht_key.is_first = is_first;
set->ref_count = 1;
return set;
}
static void mlxsw_afa_set_destroy(struct mlxsw_afa_set *set)
{
kfree(set);
}
static int mlxsw_afa_set_share(struct mlxsw_afa *mlxsw_afa,
struct mlxsw_afa_set *set)
{
int err;
err = rhashtable_insert_fast(&mlxsw_afa->set_ht, &set->ht_node,
mlxsw_afa_set_ht_params);
if (err)
return err;
err = mlxsw_afa->ops->kvdl_set_add(mlxsw_afa->ops_priv,
&set->kvdl_index,
set->ht_key.enc_actions,
set->ht_key.is_first);
if (err)
goto err_kvdl_set_add;
set->shared = true;
set->prev = NULL;
return 0;
err_kvdl_set_add:
rhashtable_remove_fast(&mlxsw_afa->set_ht, &set->ht_node,
mlxsw_afa_set_ht_params);
return err;
}
static void mlxsw_afa_set_unshare(struct mlxsw_afa *mlxsw_afa,
struct mlxsw_afa_set *set)
{
mlxsw_afa->ops->kvdl_set_del(mlxsw_afa->ops_priv,
set->kvdl_index,
set->ht_key.is_first);
rhashtable_remove_fast(&mlxsw_afa->set_ht, &set->ht_node,
mlxsw_afa_set_ht_params);
set->shared = false;
}
static void mlxsw_afa_set_put(struct mlxsw_afa *mlxsw_afa,
struct mlxsw_afa_set *set)
{
if (--set->ref_count)
return;
if (set->shared)
mlxsw_afa_set_unshare(mlxsw_afa, set);
mlxsw_afa_set_destroy(set);
}
static struct mlxsw_afa_set *mlxsw_afa_set_get(struct mlxsw_afa *mlxsw_afa,
struct mlxsw_afa_set *orig_set)
{
struct mlxsw_afa_set *set;
int err;
/* There is a hashtable of sets maintained. If a set with the exact
* same encoding exists, we reuse it. Otherwise, the current set
* is shared by making it available to others using the hash table.
*/
set = rhashtable_lookup_fast(&mlxsw_afa->set_ht, &orig_set->ht_key,
mlxsw_afa_set_ht_params);
if (set) {
set->ref_count++;
mlxsw_afa_set_put(mlxsw_afa, orig_set);
} else {
set = orig_set;
err = mlxsw_afa_set_share(mlxsw_afa, set);
if (err)
return ERR_PTR(err);
}
return set;
}
/* Block structure holds a list of action sets. One action block
* represents one chain of actions executed upon match of a rule.
*/
struct mlxsw_afa_block {
struct mlxsw_afa *afa;
bool finished;
struct mlxsw_afa_set *first_set;
struct mlxsw_afa_set *cur_set;
unsigned int cur_act_index; /* In current set. */
struct list_head fwd_entry_ref_list;
};
struct mlxsw_afa_block *mlxsw_afa_block_create(struct mlxsw_afa *mlxsw_afa)
{
struct mlxsw_afa_block *block;
block = kzalloc(sizeof(*block), GFP_KERNEL);
if (!block)
return NULL;
INIT_LIST_HEAD(&block->fwd_entry_ref_list);
block->afa = mlxsw_afa;
/* At least one action set is always present, so just create it here */
block->first_set = mlxsw_afa_set_create(true);
if (!block->first_set)
goto err_first_set_create;
block->cur_set = block->first_set;
return block;
err_first_set_create:
kfree(block);
return NULL;
}
EXPORT_SYMBOL(mlxsw_afa_block_create);
static void mlxsw_afa_fwd_entry_refs_destroy(struct mlxsw_afa_block *block);
void mlxsw_afa_block_destroy(struct mlxsw_afa_block *block)
{
struct mlxsw_afa_set *set = block->first_set;
struct mlxsw_afa_set *next_set;
do {
next_set = set->next;
mlxsw_afa_set_put(block->afa, set);
set = next_set;
} while (set);
mlxsw_afa_fwd_entry_refs_destroy(block);
kfree(block);
}
EXPORT_SYMBOL(mlxsw_afa_block_destroy);
int mlxsw_afa_block_commit(struct mlxsw_afa_block *block)
{
struct mlxsw_afa_set *set = block->cur_set;
struct mlxsw_afa_set *prev_set;
int err;
block->cur_set = NULL;
/* Go over all linked sets starting from last
* and try to find existing set in the hash table.
* In case it is not there, assign a KVD linear index
* and insert it.
*/
do {
prev_set = set->prev;
set = mlxsw_afa_set_get(block->afa, set);
if (IS_ERR(set)) {
err = PTR_ERR(set);
goto rollback;
}
if (prev_set) {
prev_set->next = set;
mlxsw_afa_set_next_set(prev_set, set->kvdl_index);
set = prev_set;
}
} while (prev_set);
block->first_set = set;
block->finished = true;
return 0;
rollback:
while ((set = set->next))
mlxsw_afa_set_put(block->afa, set);
return err;
}
EXPORT_SYMBOL(mlxsw_afa_block_commit);
char *mlxsw_afa_block_first_set(struct mlxsw_afa_block *block)
{
return block->first_set->ht_key.enc_actions;
}
EXPORT_SYMBOL(mlxsw_afa_block_first_set);
u32 mlxsw_afa_block_first_set_kvdl_index(struct mlxsw_afa_block *block)
{
return block->first_set->kvdl_index;
}
EXPORT_SYMBOL(mlxsw_afa_block_first_set_kvdl_index);
void mlxsw_afa_block_continue(struct mlxsw_afa_block *block)
{
if (WARN_ON(block->finished))
return;
mlxsw_afa_set_goto_set(block->cur_set,
MLXSW_AFA_SET_GOTO_BINDING_CMD_NONE, 0);
block->finished = true;
}
EXPORT_SYMBOL(mlxsw_afa_block_continue);
void mlxsw_afa_block_jump(struct mlxsw_afa_block *block, u16 group_id)
{
if (WARN_ON(block->finished))
return;
mlxsw_afa_set_goto_set(block->cur_set,
MLXSW_AFA_SET_GOTO_BINDING_CMD_JUMP, group_id);
block->finished = true;
}
EXPORT_SYMBOL(mlxsw_afa_block_jump);
static struct mlxsw_afa_fwd_entry *
mlxsw_afa_fwd_entry_create(struct mlxsw_afa *mlxsw_afa, u8 local_port)
{
struct mlxsw_afa_fwd_entry *fwd_entry;
int err;
fwd_entry = kzalloc(sizeof(*fwd_entry), GFP_KERNEL);
if (!fwd_entry)
return ERR_PTR(-ENOMEM);
fwd_entry->ht_key.local_port = local_port;
fwd_entry->ref_count = 1;
err = rhashtable_insert_fast(&mlxsw_afa->fwd_entry_ht,
&fwd_entry->ht_node,
mlxsw_afa_fwd_entry_ht_params);
if (err)
goto err_rhashtable_insert;
err = mlxsw_afa->ops->kvdl_fwd_entry_add(mlxsw_afa->ops_priv,
&fwd_entry->kvdl_index,
local_port);
if (err)
goto err_kvdl_fwd_entry_add;
return fwd_entry;
err_kvdl_fwd_entry_add:
rhashtable_remove_fast(&mlxsw_afa->fwd_entry_ht, &fwd_entry->ht_node,
mlxsw_afa_fwd_entry_ht_params);
err_rhashtable_insert:
kfree(fwd_entry);
return ERR_PTR(err);
}
static void mlxsw_afa_fwd_entry_destroy(struct mlxsw_afa *mlxsw_afa,
struct mlxsw_afa_fwd_entry *fwd_entry)
{
mlxsw_afa->ops->kvdl_fwd_entry_del(mlxsw_afa->ops_priv,
fwd_entry->kvdl_index);
rhashtable_remove_fast(&mlxsw_afa->fwd_entry_ht, &fwd_entry->ht_node,
mlxsw_afa_fwd_entry_ht_params);
kfree(fwd_entry);
}
static struct mlxsw_afa_fwd_entry *
mlxsw_afa_fwd_entry_get(struct mlxsw_afa *mlxsw_afa, u8 local_port)
{
struct mlxsw_afa_fwd_entry_ht_key ht_key = {0};
struct mlxsw_afa_fwd_entry *fwd_entry;
ht_key.local_port = local_port;
fwd_entry = rhashtable_lookup_fast(&mlxsw_afa->fwd_entry_ht, &ht_key,
mlxsw_afa_fwd_entry_ht_params);
if (fwd_entry) {
fwd_entry->ref_count++;
return fwd_entry;
}
return mlxsw_afa_fwd_entry_create(mlxsw_afa, local_port);
}
static void mlxsw_afa_fwd_entry_put(struct mlxsw_afa *mlxsw_afa,
struct mlxsw_afa_fwd_entry *fwd_entry)
{
if (--fwd_entry->ref_count)
return;
mlxsw_afa_fwd_entry_destroy(mlxsw_afa, fwd_entry);
}
struct mlxsw_afa_fwd_entry_ref {
struct list_head list;
struct mlxsw_afa_fwd_entry *fwd_entry;
};
static struct mlxsw_afa_fwd_entry_ref *
mlxsw_afa_fwd_entry_ref_create(struct mlxsw_afa_block *block, u8 local_port)
{
struct mlxsw_afa_fwd_entry_ref *fwd_entry_ref;
struct mlxsw_afa_fwd_entry *fwd_entry;
int err;
fwd_entry_ref = kzalloc(sizeof(*fwd_entry_ref), GFP_KERNEL);
if (!fwd_entry_ref)
return ERR_PTR(-ENOMEM);
fwd_entry = mlxsw_afa_fwd_entry_get(block->afa, local_port);
if (IS_ERR(fwd_entry)) {
err = PTR_ERR(fwd_entry);
goto err_fwd_entry_get;
}
fwd_entry_ref->fwd_entry = fwd_entry;
list_add(&fwd_entry_ref->list, &block->fwd_entry_ref_list);
return fwd_entry_ref;
err_fwd_entry_get:
kfree(fwd_entry_ref);
return ERR_PTR(err);
}
static void
mlxsw_afa_fwd_entry_ref_destroy(struct mlxsw_afa_block *block,
struct mlxsw_afa_fwd_entry_ref *fwd_entry_ref)
{
list_del(&fwd_entry_ref->list);
mlxsw_afa_fwd_entry_put(block->afa, fwd_entry_ref->fwd_entry);
kfree(fwd_entry_ref);
}
static void mlxsw_afa_fwd_entry_refs_destroy(struct mlxsw_afa_block *block)
{
struct mlxsw_afa_fwd_entry_ref *fwd_entry_ref;
struct mlxsw_afa_fwd_entry_ref *tmp;
list_for_each_entry_safe(fwd_entry_ref, tmp,
&block->fwd_entry_ref_list, list)
mlxsw_afa_fwd_entry_ref_destroy(block, fwd_entry_ref);
}
#define MLXSW_AFA_ONE_ACTION_LEN 32
#define MLXSW_AFA_PAYLOAD_OFFSET 4
static char *mlxsw_afa_block_append_action(struct mlxsw_afa_block *block,
u8 action_code, u8 action_size)
{
char *oneact;
char *actions;
if (WARN_ON(block->finished))
return NULL;
if (block->cur_act_index + action_size >
block->afa->max_acts_per_set) {
struct mlxsw_afa_set *set;
/* The appended action won't fit into the current action set,
* so create a new set.
*/
set = mlxsw_afa_set_create(false);
if (!set)
return NULL;
set->prev = block->cur_set;
block->cur_act_index = 0;
block->cur_set->next = set;
block->cur_set = set;
}
actions = block->cur_set->ht_key.enc_actions;
oneact = actions + block->cur_act_index * MLXSW_AFA_ONE_ACTION_LEN;
block->cur_act_index += action_size;
mlxsw_afa_all_action_type_set(oneact, action_code);
return oneact + MLXSW_AFA_PAYLOAD_OFFSET;
}
/* Trap / Discard Action
* ---------------------
* The Trap / Discard action enables trapping / mirroring packets to the CPU
* as well as discarding packets.
* The ACL Trap / Discard separates the forward/discard control from CPU
* trap control. In addition, the Trap / Discard action enables activating
* SPAN (port mirroring).
*/
#define MLXSW_AFA_TRAPDISC_CODE 0x03
#define MLXSW_AFA_TRAPDISC_SIZE 1
enum mlxsw_afa_trapdisc_forward_action {
MLXSW_AFA_TRAPDISC_FORWARD_ACTION_DISCARD = 3,
};
/* afa_trapdisc_forward_action
* Forward Action.
*/
MLXSW_ITEM32(afa, trapdisc, forward_action, 0x00, 0, 4);
static inline void
mlxsw_afa_trapdisc_pack(char *payload,
enum mlxsw_afa_trapdisc_forward_action forward_action)
{
mlxsw_afa_trapdisc_forward_action_set(payload, forward_action);
}
int mlxsw_afa_block_append_drop(struct mlxsw_afa_block *block)
{
char *act = mlxsw_afa_block_append_action(block,
MLXSW_AFA_TRAPDISC_CODE,
MLXSW_AFA_TRAPDISC_SIZE);
if (!act)
return -ENOBUFS;
mlxsw_afa_trapdisc_pack(act, MLXSW_AFA_TRAPDISC_FORWARD_ACTION_DISCARD);
return 0;
}
EXPORT_SYMBOL(mlxsw_afa_block_append_drop);
/* Forwarding Action
* -----------------
* Forwarding Action can be used to implement Policy Based Switching (PBS)
* as well as OpenFlow related "Output" action.
*/
#define MLXSW_AFA_FORWARD_CODE 0x07
#define MLXSW_AFA_FORWARD_SIZE 1
enum mlxsw_afa_forward_type {
/* PBS, Policy Based Switching */
MLXSW_AFA_FORWARD_TYPE_PBS,
/* Output, OpenFlow output type */
MLXSW_AFA_FORWARD_TYPE_OUTPUT,
};
/* afa_forward_type */
MLXSW_ITEM32(afa, forward, type, 0x00, 24, 2);
/* afa_forward_pbs_ptr
* A pointer to the PBS entry configured by PPBS register.
* Reserved when in_port is set.
*/
MLXSW_ITEM32(afa, forward, pbs_ptr, 0x08, 0, 24);
/* afa_forward_in_port
* Packet is forwarded back to the ingress port.
*/
MLXSW_ITEM32(afa, forward, in_port, 0x0C, 0, 1);
static inline void
mlxsw_afa_forward_pack(char *payload, enum mlxsw_afa_forward_type type,
u32 pbs_ptr, bool in_port)
{
mlxsw_afa_forward_type_set(payload, type);
mlxsw_afa_forward_pbs_ptr_set(payload, pbs_ptr);
mlxsw_afa_forward_in_port_set(payload, in_port);
}
int mlxsw_afa_block_append_fwd(struct mlxsw_afa_block *block,
u8 local_port, bool in_port)
{
struct mlxsw_afa_fwd_entry_ref *fwd_entry_ref;
u32 kvdl_index = 0;
char *act;
int err;
if (!in_port) {
fwd_entry_ref = mlxsw_afa_fwd_entry_ref_create(block,
local_port);
if (IS_ERR(fwd_entry_ref))
return PTR_ERR(fwd_entry_ref);
kvdl_index = fwd_entry_ref->fwd_entry->kvdl_index;
}
act = mlxsw_afa_block_append_action(block, MLXSW_AFA_FORWARD_CODE,
MLXSW_AFA_FORWARD_SIZE);
if (!act) {
err = -ENOBUFS;
goto err_append_action;
}
mlxsw_afa_forward_pack(act, MLXSW_AFA_FORWARD_TYPE_OUTPUT,
kvdl_index, in_port);
return 0;
err_append_action:
if (!in_port)
mlxsw_afa_fwd_entry_ref_destroy(block, fwd_entry_ref);
return err;
}
EXPORT_SYMBOL(mlxsw_afa_block_append_fwd);
/*
* drivers/net/ethernet/mellanox/mlxsw/core_acl_flex_actions.h
* Copyright (c) 2017 Mellanox Technologies. All rights reserved.
* Copyright (c) 2017 Jiri Pirko <jiri@mellanox.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
* 3. Neither the names of the copyright holders nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* Alternatively, this software may be distributed under the terms of the
* GNU General Public License ("GPL") version 2 as published by the Free
* Software Foundation.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef _MLXSW_CORE_ACL_FLEX_ACTIONS_H
#define _MLXSW_CORE_ACL_FLEX_ACTIONS_H
#include <linux/types.h>
struct mlxsw_afa;
struct mlxsw_afa_block;
struct mlxsw_afa_ops {
int (*kvdl_set_add)(void *priv, u32 *p_kvdl_index,
char *enc_actions, bool is_first);
void (*kvdl_set_del)(void *priv, u32 kvdl_index, bool is_first);
int (*kvdl_fwd_entry_add)(void *priv, u32 *p_kvdl_index, u8 local_port);
void (*kvdl_fwd_entry_del)(void *priv, u32 kvdl_index);
};
struct mlxsw_afa *mlxsw_afa_create(unsigned int max_acts_per_set,
const struct mlxsw_afa_ops *ops,
void *ops_priv);
void mlxsw_afa_destroy(struct mlxsw_afa *mlxsw_afa);
struct mlxsw_afa_block *mlxsw_afa_block_create(struct mlxsw_afa *mlxsw_afa);
void mlxsw_afa_block_destroy(struct mlxsw_afa_block *block);
int mlxsw_afa_block_commit(struct mlxsw_afa_block *block);
char *mlxsw_afa_block_first_set(struct mlxsw_afa_block *block);
u32 mlxsw_afa_block_first_set_kvdl_index(struct mlxsw_afa_block *block);
void mlxsw_afa_block_continue(struct mlxsw_afa_block *block);
void mlxsw_afa_block_jump(struct mlxsw_afa_block *block, u16 group_id);
int mlxsw_afa_block_append_drop(struct mlxsw_afa_block *block);
int mlxsw_afa_block_append_fwd(struct mlxsw_afa_block *block,
u8 local_port, bool in_port);
#endif
/*
* drivers/net/ethernet/mellanox/mlxsw/core_acl_flex_keys.c
* Copyright (c) 2017 Mellanox Technologies. All rights reserved.
* Copyright (c) 2017 Jiri Pirko <jiri@mellanox.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
* 3. Neither the names of the copyright holders nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* Alternatively, this software may be distributed under the terms of the
* GNU General Public License ("GPL") version 2 as published by the Free
* Software Foundation.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/errno.h>
#include "item.h"
#include "core_acl_flex_keys.h"
struct mlxsw_afk {
struct list_head key_info_list;
unsigned int max_blocks;
const struct mlxsw_afk_block *blocks;
unsigned int blocks_count;
};
static bool mlxsw_afk_blocks_check(struct mlxsw_afk *mlxsw_afk)
{
int i;
int j;
for (i = 0; i < mlxsw_afk->blocks_count; i++) {
const struct mlxsw_afk_block *block = &mlxsw_afk->blocks[i];
for (j = 0; j < block->instances_count; j++) {
struct mlxsw_afk_element_inst *elinst;
elinst = &block->instances[j];
if (elinst->type != elinst->info->type ||
elinst->item.size.bits !=
elinst->info->item.size.bits)
return false;
}
}
return true;
}
struct mlxsw_afk *mlxsw_afk_create(unsigned int max_blocks,
const struct mlxsw_afk_block *blocks,
unsigned int blocks_count)
{
struct mlxsw_afk *mlxsw_afk;
mlxsw_afk = kzalloc(sizeof(*mlxsw_afk), GFP_KERNEL);
if (!mlxsw_afk)
return NULL;
INIT_LIST_HEAD(&mlxsw_afk->key_info_list);
mlxsw_afk->max_blocks = max_blocks;
mlxsw_afk->blocks = blocks;
mlxsw_afk->blocks_count = blocks_count;
WARN_ON(!mlxsw_afk_blocks_check(mlxsw_afk));
return mlxsw_afk;
}
EXPORT_SYMBOL(mlxsw_afk_create);
void mlxsw_afk_destroy(struct mlxsw_afk *mlxsw_afk)
{
WARN_ON(!list_empty(&mlxsw_afk->key_info_list));
kfree(mlxsw_afk);
}
EXPORT_SYMBOL(mlxsw_afk_destroy);
struct mlxsw_afk_key_info {
struct list_head list;
unsigned int ref_count;
unsigned int blocks_count;
int element_to_block[MLXSW_AFK_ELEMENT_MAX]; /* index is element, value
* is index inside "blocks"
*/
struct mlxsw_afk_element_usage elusage;
const struct mlxsw_afk_block *blocks[0];
};
static bool
mlxsw_afk_key_info_elements_eq(struct mlxsw_afk_key_info *key_info,
struct mlxsw_afk_element_usage *elusage)
{
return memcmp(&key_info->elusage, elusage, sizeof(*elusage)) == 0;
}
static struct mlxsw_afk_key_info *
mlxsw_afk_key_info_find(struct mlxsw_afk *mlxsw_afk,
struct mlxsw_afk_element_usage *elusage)
{
struct mlxsw_afk_key_info *key_info;
list_for_each_entry(key_info, &mlxsw_afk->key_info_list, list) {
if (mlxsw_afk_key_info_elements_eq(key_info, elusage))
return key_info;
}
return NULL;
}
struct mlxsw_afk_picker {
struct {
DECLARE_BITMAP(element, MLXSW_AFK_ELEMENT_MAX);
unsigned int total;
} hits[0];
};
static void mlxsw_afk_picker_count_hits(struct mlxsw_afk *mlxsw_afk,
struct mlxsw_afk_picker *picker,
enum mlxsw_afk_element element)
{
int i;
int j;
for (i = 0; i < mlxsw_afk->blocks_count; i++) {
const struct mlxsw_afk_block *block = &mlxsw_afk->blocks[i];
for (j = 0; j < block->instances_count; j++) {
struct mlxsw_afk_element_inst *elinst;
elinst = &block->instances[j];
if (elinst->info->element == element) {
__set_bit(element, picker->hits[i].element);
picker->hits[i].total++;
}
}
}
}
static void mlxsw_afk_picker_subtract_hits(struct mlxsw_afk *mlxsw_afk,
struct mlxsw_afk_picker *picker,
int block_index)
{
DECLARE_BITMAP(hits_element, MLXSW_AFK_ELEMENT_MAX);
int i;
int j;
memcpy(&hits_element, &picker->hits[block_index].element,
sizeof(hits_element));
for (i = 0; i < mlxsw_afk->blocks_count; i++) {
for_each_set_bit(j, hits_element, MLXSW_AFK_ELEMENT_MAX) {
if (__test_and_clear_bit(j, picker->hits[i].element))
picker->hits[i].total--;
}
}
}
static int mlxsw_afk_picker_most_hits_get(struct mlxsw_afk *mlxsw_afk,
struct mlxsw_afk_picker *picker)
{
int most_index = -EINVAL; /* Should never happen to return this */
int most_hits = 0;
int i;
for (i = 0; i < mlxsw_afk->blocks_count; i++) {
if (picker->hits[i].total > most_hits) {
most_hits = picker->hits[i].total;
most_index = i;
}
}
return most_index;
}
static int mlxsw_afk_picker_key_info_add(struct mlxsw_afk *mlxsw_afk,
struct mlxsw_afk_picker *picker,
int block_index,
struct mlxsw_afk_key_info *key_info)
{
enum mlxsw_afk_element element;
if (key_info->blocks_count == mlxsw_afk->max_blocks)
return -EINVAL;
for_each_set_bit(element, picker->hits[block_index].element,
MLXSW_AFK_ELEMENT_MAX) {
key_info->element_to_block[element] = key_info->blocks_count;
mlxsw_afk_element_usage_add(&key_info->elusage, element);
}
key_info->blocks[key_info->blocks_count] =
&mlxsw_afk->blocks[block_index];
key_info->blocks_count++;
return 0;
}
static int mlxsw_afk_picker(struct mlxsw_afk *mlxsw_afk,
struct mlxsw_afk_key_info *key_info,
struct mlxsw_afk_element_usage *elusage)
{
struct mlxsw_afk_picker *picker;
enum mlxsw_afk_element element;
size_t alloc_size;
int err;
alloc_size = sizeof(picker->hits[0]) * mlxsw_afk->blocks_count;
picker = kzalloc(alloc_size, GFP_KERNEL);
if (!picker)
return -ENOMEM;
/* Since the same elements could be present in multiple blocks,
* we must find out optimal block list in order to make the
* block count as low as possible.
*
* First, we count hits. We go over all available blocks and count
* how many of requested elements are covered by each.
*
* Then in loop, we find block with most hits and add it to
* output key_info. Then we have to subtract this block hits so
* the next iteration will find most suitable block for
* the rest of requested elements.
*/
mlxsw_afk_element_usage_for_each(element, elusage)
mlxsw_afk_picker_count_hits(mlxsw_afk, picker, element);
do {
int block_index;
block_index = mlxsw_afk_picker_most_hits_get(mlxsw_afk, picker);
if (block_index < 0) {
err = block_index;
goto out;
}
err = mlxsw_afk_picker_key_info_add(mlxsw_afk, picker,
block_index, key_info);
if (err)
goto out;
mlxsw_afk_picker_subtract_hits(mlxsw_afk, picker, block_index);
} while (!mlxsw_afk_key_info_elements_eq(key_info, elusage));
err = 0;
out:
kfree(picker);
return err;
}
static struct mlxsw_afk_key_info *
mlxsw_afk_key_info_create(struct mlxsw_afk *mlxsw_afk,
struct mlxsw_afk_element_usage *elusage)
{
struct mlxsw_afk_key_info *key_info;
size_t alloc_size;
int err;
alloc_size = sizeof(*key_info) +
sizeof(key_info->blocks[0]) * mlxsw_afk->max_blocks;
key_info = kzalloc(alloc_size, GFP_KERNEL);
if (!key_info)
return ERR_PTR(-ENOMEM);
err = mlxsw_afk_picker(mlxsw_afk, key_info, elusage);
if (err)
goto err_picker;
list_add(&key_info->list, &mlxsw_afk->key_info_list);
key_info->ref_count = 1;
return key_info;
err_picker:
kfree(key_info);
return ERR_PTR(err);
}
static void mlxsw_afk_key_info_destroy(struct mlxsw_afk_key_info *key_info)
{
list_del(&key_info->list);
kfree(key_info);
}
struct mlxsw_afk_key_info *
mlxsw_afk_key_info_get(struct mlxsw_afk *mlxsw_afk,
struct mlxsw_afk_element_usage *elusage)
{
struct mlxsw_afk_key_info *key_info;
key_info = mlxsw_afk_key_info_find(mlxsw_afk, elusage);
if (key_info) {
key_info->ref_count++;
return key_info;
}
return mlxsw_afk_key_info_create(mlxsw_afk, elusage);
}
EXPORT_SYMBOL(mlxsw_afk_key_info_get);
void mlxsw_afk_key_info_put(struct mlxsw_afk_key_info *key_info)
{
if (--key_info->ref_count)
return;
mlxsw_afk_key_info_destroy(key_info);
}
EXPORT_SYMBOL(mlxsw_afk_key_info_put);
bool mlxsw_afk_key_info_subset(struct mlxsw_afk_key_info *key_info,
struct mlxsw_afk_element_usage *elusage)
{
return mlxsw_afk_element_usage_subset(elusage, &key_info->elusage);
}
EXPORT_SYMBOL(mlxsw_afk_key_info_subset);
static const struct mlxsw_afk_element_inst *
mlxsw_afk_block_elinst_get(const struct mlxsw_afk_block *block,
enum mlxsw_afk_element element)
{
int i;
for (i = 0; i < block->instances_count; i++) {
struct mlxsw_afk_element_inst *elinst;
elinst = &block->instances[i];
if (elinst->info->element == element)
return elinst;
}
return NULL;
}
static const struct mlxsw_afk_element_inst *
mlxsw_afk_key_info_elinst_get(struct mlxsw_afk_key_info *key_info,
enum mlxsw_afk_element element,
int *p_block_index)
{
const struct mlxsw_afk_element_inst *elinst;
const struct mlxsw_afk_block *block;
int block_index;
if (WARN_ON(!test_bit(element, key_info->elusage.usage)))
return NULL;
block_index = key_info->element_to_block[element];
block = key_info->blocks[block_index];
elinst = mlxsw_afk_block_elinst_get(block, element);
if (WARN_ON(!elinst))
return NULL;
*p_block_index = block_index;
return elinst;
}
u16
mlxsw_afk_key_info_block_encoding_get(const struct mlxsw_afk_key_info *key_info,
int block_index)
{
return key_info->blocks[block_index]->encoding;
}
EXPORT_SYMBOL(mlxsw_afk_key_info_block_encoding_get);
unsigned int
mlxsw_afk_key_info_blocks_count_get(const struct mlxsw_afk_key_info *key_info)
{
return key_info->blocks_count;
}
EXPORT_SYMBOL(mlxsw_afk_key_info_blocks_count_get);
void mlxsw_afk_values_add_u32(struct mlxsw_afk_element_values *values,
enum mlxsw_afk_element element,
u32 key_value, u32 mask_value)
{
const struct mlxsw_afk_element_info *elinfo =
&mlxsw_afk_element_infos[element];
const struct mlxsw_item *storage_item = &elinfo->item;
if (!mask_value)
return;
if (WARN_ON(elinfo->type != MLXSW_AFK_ELEMENT_TYPE_U32))
return;
__mlxsw_item_set32(values->storage.key, storage_item, 0, key_value);
__mlxsw_item_set32(values->storage.mask, storage_item, 0, mask_value);
mlxsw_afk_element_usage_add(&values->elusage, element);
}
EXPORT_SYMBOL(mlxsw_afk_values_add_u32);
void mlxsw_afk_values_add_buf(struct mlxsw_afk_element_values *values,
enum mlxsw_afk_element element,
const char *key_value, const char *mask_value,
unsigned int len)
{
const struct mlxsw_afk_element_info *elinfo =
&mlxsw_afk_element_infos[element];
const struct mlxsw_item *storage_item = &elinfo->item;
if (!memchr_inv(mask_value, 0, len)) /* If mask is zero */
return;
if (WARN_ON(elinfo->type != MLXSW_AFK_ELEMENT_TYPE_BUF) ||
WARN_ON(elinfo->item.size.bytes != len))
return;
__mlxsw_item_memcpy_to(values->storage.key, key_value,
storage_item, 0);
__mlxsw_item_memcpy_to(values->storage.mask, mask_value,
storage_item, 0);
mlxsw_afk_element_usage_add(&values->elusage, element);
}
EXPORT_SYMBOL(mlxsw_afk_values_add_buf);
static void mlxsw_afk_encode_u32(const struct mlxsw_item *storage_item,
const struct mlxsw_item *output_item,
char *storage, char *output_indexed)
{
u32 value;
value = __mlxsw_item_get32(storage, storage_item, 0);
__mlxsw_item_set32(output_indexed, output_item, 0, value);
}
static void mlxsw_afk_encode_buf(const struct mlxsw_item *storage_item,
const struct mlxsw_item *output_item,
char *storage, char *output_indexed)
{
char *storage_data = __mlxsw_item_data(storage, storage_item, 0);
char *output_data = __mlxsw_item_data(output_indexed, output_item, 0);
size_t len = output_item->size.bytes;
memcpy(output_data, storage_data, len);
}
#define MLXSW_AFK_KEY_BLOCK_SIZE 16
static void mlxsw_afk_encode_one(const struct mlxsw_afk_element_inst *elinst,
int block_index, char *storage, char *output)
{
char *output_indexed = output + block_index * MLXSW_AFK_KEY_BLOCK_SIZE;
const struct mlxsw_item *storage_item = &elinst->info->item;
const struct mlxsw_item *output_item = &elinst->item;
if (elinst->type == MLXSW_AFK_ELEMENT_TYPE_U32)
mlxsw_afk_encode_u32(storage_item, output_item,
storage, output_indexed);
else if (elinst->type == MLXSW_AFK_ELEMENT_TYPE_BUF)
mlxsw_afk_encode_buf(storage_item, output_item,
storage, output_indexed);
}
void mlxsw_afk_encode(struct mlxsw_afk_key_info *key_info,
struct mlxsw_afk_element_values *values,
char *key, char *mask)
{
const struct mlxsw_afk_element_inst *elinst;
enum mlxsw_afk_element element;
int block_index;
mlxsw_afk_element_usage_for_each(element, &values->elusage) {
elinst = mlxsw_afk_key_info_elinst_get(key_info, element,
&block_index);
if (!elinst)
continue;
mlxsw_afk_encode_one(elinst, block_index,
values->storage.key, key);
mlxsw_afk_encode_one(elinst, block_index,
values->storage.mask, mask);
}
}
EXPORT_SYMBOL(mlxsw_afk_encode);
/*
* drivers/net/ethernet/mellanox/mlxsw/core_acl_flex_keys.h
* Copyright (c) 2017 Mellanox Technologies. All rights reserved.
* Copyright (c) 2017 Jiri Pirko <jiri@mellanox.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
* 3. Neither the names of the copyright holders nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* Alternatively, this software may be distributed under the terms of the
* GNU General Public License ("GPL") version 2 as published by the Free
* Software Foundation.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef _MLXSW_CORE_ACL_FLEX_KEYS_H
#define _MLXSW_CORE_ACL_FLEX_KEYS_H
#include <linux/types.h>
#include <linux/bitmap.h>
#include "item.h"
enum mlxsw_afk_element {
MLXSW_AFK_ELEMENT_SRC_SYS_PORT,
MLXSW_AFK_ELEMENT_DMAC,
MLXSW_AFK_ELEMENT_SMAC,
MLXSW_AFK_ELEMENT_ETHERTYPE,
MLXSW_AFK_ELEMENT_IP_PROTO,
MLXSW_AFK_ELEMENT_SRC_IP4,
MLXSW_AFK_ELEMENT_DST_IP4,
MLXSW_AFK_ELEMENT_SRC_IP6_HI,
MLXSW_AFK_ELEMENT_SRC_IP6_LO,
MLXSW_AFK_ELEMENT_DST_IP6_HI,
MLXSW_AFK_ELEMENT_DST_IP6_LO,
MLXSW_AFK_ELEMENT_DST_L4_PORT,
MLXSW_AFK_ELEMENT_SRC_L4_PORT,
MLXSW_AFK_ELEMENT_MAX,
};
enum mlxsw_afk_element_type {
MLXSW_AFK_ELEMENT_TYPE_U32,
MLXSW_AFK_ELEMENT_TYPE_BUF,
};
struct mlxsw_afk_element_info {
enum mlxsw_afk_element element; /* element ID */
enum mlxsw_afk_element_type type;
struct mlxsw_item item; /* element geometry in internal storage */
};
#define MLXSW_AFK_ELEMENT_INFO(_type, _element, _offset, _shift, _size) \
[MLXSW_AFK_ELEMENT_##_element] = { \
.element = MLXSW_AFK_ELEMENT_##_element, \
.type = _type, \
.item = { \
.offset = _offset, \
.shift = _shift, \
.size = {.bits = _size}, \
.name = #_element, \
}, \
}
#define MLXSW_AFK_ELEMENT_INFO_U32(_element, _offset, _shift, _size) \
MLXSW_AFK_ELEMENT_INFO(MLXSW_AFK_ELEMENT_TYPE_U32, \
_element, _offset, _shift, _size)
#define MLXSW_AFK_ELEMENT_INFO_BUF(_element, _offset, _size) \
MLXSW_AFK_ELEMENT_INFO(MLXSW_AFK_ELEMENT_TYPE_BUF, \
_element, _offset, 0, _size)
/* For the purpose of the driver, define a internal storage scratchpad
* that will be used to store key/mask values. For each defined element type
* define an internal storage geometry.
*/
static const struct mlxsw_afk_element_info mlxsw_afk_element_infos[] = {
MLXSW_AFK_ELEMENT_INFO_U32(SRC_SYS_PORT, 0x00, 16, 16),
MLXSW_AFK_ELEMENT_INFO_BUF(DMAC, 0x04, 6),
MLXSW_AFK_ELEMENT_INFO_BUF(SMAC, 0x0A, 6),
MLXSW_AFK_ELEMENT_INFO_U32(ETHERTYPE, 0x00, 0, 16),
MLXSW_AFK_ELEMENT_INFO_U32(IP_PROTO, 0x10, 0, 8),
MLXSW_AFK_ELEMENT_INFO_U32(SRC_IP4, 0x18, 0, 32),
MLXSW_AFK_ELEMENT_INFO_U32(DST_IP4, 0x1C, 0, 32),
MLXSW_AFK_ELEMENT_INFO_BUF(SRC_IP6_HI, 0x18, 8),
MLXSW_AFK_ELEMENT_INFO_BUF(SRC_IP6_LO, 0x20, 8),
MLXSW_AFK_ELEMENT_INFO_BUF(DST_IP6_HI, 0x28, 8),
MLXSW_AFK_ELEMENT_INFO_BUF(DST_IP6_LO, 0x30, 8),
MLXSW_AFK_ELEMENT_INFO_U32(DST_L4_PORT, 0x14, 0, 16),
MLXSW_AFK_ELEMENT_INFO_U32(SRC_L4_PORT, 0x14, 16, 16),
};
#define MLXSW_AFK_ELEMENT_STORAGE_SIZE 0x38
struct mlxsw_afk_element_inst { /* element instance in actual block */
const struct mlxsw_afk_element_info *info;
enum mlxsw_afk_element_type type;
struct mlxsw_item item; /* element geometry in block */
};
#define MLXSW_AFK_ELEMENT_INST(_type, _element, _offset, _shift, _size) \
{ \
.info = &mlxsw_afk_element_infos[MLXSW_AFK_ELEMENT_##_element], \
.type = _type, \
.item = { \
.offset = _offset, \
.shift = _shift, \
.size = {.bits = _size}, \
.name = #_element, \
}, \
}
#define MLXSW_AFK_ELEMENT_INST_U32(_element, _offset, _shift, _size) \
MLXSW_AFK_ELEMENT_INST(MLXSW_AFK_ELEMENT_TYPE_U32, \
_element, _offset, _shift, _size)
#define MLXSW_AFK_ELEMENT_INST_BUF(_element, _offset, _size) \
MLXSW_AFK_ELEMENT_INST(MLXSW_AFK_ELEMENT_TYPE_BUF, \
_element, _offset, 0, _size)
struct mlxsw_afk_block {
u16 encoding; /* block ID */
struct mlxsw_afk_element_inst *instances;
unsigned int instances_count;
};
#define MLXSW_AFK_BLOCK(_encoding, _instances) \
{ \
.encoding = _encoding, \
.instances = _instances, \
.instances_count = ARRAY_SIZE(_instances), \
}
struct mlxsw_afk_element_usage {
DECLARE_BITMAP(usage, MLXSW_AFK_ELEMENT_MAX);
};
#define mlxsw_afk_element_usage_for_each(element, elusage) \
for_each_set_bit(element, (elusage)->usage, MLXSW_AFK_ELEMENT_MAX)
static inline void
mlxsw_afk_element_usage_add(struct mlxsw_afk_element_usage *elusage,
enum mlxsw_afk_element element)
{
__set_bit(element, elusage->usage);
}
static inline void
mlxsw_afk_element_usage_zero(struct mlxsw_afk_element_usage *elusage)
{
bitmap_zero(elusage->usage, MLXSW_AFK_ELEMENT_MAX);
}
static inline void
mlxsw_afk_element_usage_fill(struct mlxsw_afk_element_usage *elusage,
const enum mlxsw_afk_element *elements,
unsigned int elements_count)
{
int i;
mlxsw_afk_element_usage_zero(elusage);
for (i = 0; i < elements_count; i++)
mlxsw_afk_element_usage_add(elusage, elements[i]);
}
static inline bool
mlxsw_afk_element_usage_subset(struct mlxsw_afk_element_usage *elusage_small,
struct mlxsw_afk_element_usage *elusage_big)
{
int i;
for (i = 0; i < MLXSW_AFK_ELEMENT_MAX; i++)
if (test_bit(i, elusage_small->usage) &&
!test_bit(i, elusage_big->usage))
return false;
return true;
}
struct mlxsw_afk;
struct mlxsw_afk *mlxsw_afk_create(unsigned int max_blocks,
const struct mlxsw_afk_block *blocks,
unsigned int blocks_count);
void mlxsw_afk_destroy(struct mlxsw_afk *mlxsw_afk);
struct mlxsw_afk_key_info;
struct mlxsw_afk_key_info *
mlxsw_afk_key_info_get(struct mlxsw_afk *mlxsw_afk,
struct mlxsw_afk_element_usage *elusage);
void mlxsw_afk_key_info_put(struct mlxsw_afk_key_info *key_info);
bool mlxsw_afk_key_info_subset(struct mlxsw_afk_key_info *key_info,
struct mlxsw_afk_element_usage *elusage);
u16
mlxsw_afk_key_info_block_encoding_get(const struct mlxsw_afk_key_info *key_info,
int block_index);
unsigned int
mlxsw_afk_key_info_blocks_count_get(const struct mlxsw_afk_key_info *key_info);
struct mlxsw_afk_element_values {
struct mlxsw_afk_element_usage elusage;
struct {
char key[MLXSW_AFK_ELEMENT_STORAGE_SIZE];
char mask[MLXSW_AFK_ELEMENT_STORAGE_SIZE];
} storage;
};
void mlxsw_afk_values_add_u32(struct mlxsw_afk_element_values *values,
enum mlxsw_afk_element element,
u32 key_value, u32 mask_value);
void mlxsw_afk_values_add_buf(struct mlxsw_afk_element_values *values,
enum mlxsw_afk_element element,
const char *key_value, const char *mask_value,
unsigned int len);
void mlxsw_afk_encode(struct mlxsw_afk_key_info *key_info,
struct mlxsw_afk_element_values *values,
char *key, char *mask);
#endif
/*
* drivers/net/ethernet/mellanox/mlxsw/item.h
* Copyright (c) 2015 Mellanox Technologies. All rights reserved.
* Copyright (c) 2015 Jiri Pirko <jiri@mellanox.com>
* Copyright (c) 2015-2017 Mellanox Technologies. All rights reserved.
* Copyright (c) 2015-2017 Jiri Pirko <jiri@mellanox.com>
* Copyright (c) 2015 Ido Schimmel <idosch@mellanox.com>
*
* Redistribution and use in source and binary forms, with or without
......@@ -72,6 +72,40 @@ __mlxsw_item_offset(const struct mlxsw_item *item, unsigned short index,
typesize);
}
static inline u8 __mlxsw_item_get8(const char *buf,
const struct mlxsw_item *item,
unsigned short index)
{
unsigned int offset = __mlxsw_item_offset(item, index, sizeof(u8));
u8 *b = (u8 *) buf;
u8 tmp;
tmp = b[offset];
tmp >>= item->shift;
tmp &= GENMASK(item->size.bits - 1, 0);
if (item->no_real_shift)
tmp <<= item->shift;
return tmp;
}
static inline void __mlxsw_item_set8(char *buf, const struct mlxsw_item *item,
unsigned short index, u8 val)
{
unsigned int offset = __mlxsw_item_offset(item, index,
sizeof(u8));
u8 *b = (u8 *) buf;
u8 mask = GENMASK(item->size.bits - 1, 0) << item->shift;
u8 tmp;
if (!item->no_real_shift)
val <<= item->shift;
val &= mask;
tmp = b[offset];
tmp &= ~mask;
tmp |= val;
b[offset] = tmp;
}
static inline u16 __mlxsw_item_get16(const char *buf,
const struct mlxsw_item *item,
unsigned short index)
......@@ -191,6 +225,14 @@ static inline void __mlxsw_item_memcpy_to(char *buf, const char *src,
memcpy(&buf[offset], src, item->size.bytes);
}
static inline char *__mlxsw_item_data(char *buf, const struct mlxsw_item *item,
unsigned short index)
{
unsigned int offset = __mlxsw_item_offset(item, index, sizeof(char));
return &buf[offset];
}
static inline u16
__mlxsw_item_bit_array_offset(const struct mlxsw_item *item,
u16 index, u8 *shift)
......@@ -253,6 +295,47 @@ static inline void __mlxsw_item_bit_array_set(char *buf,
* _iname: item name within the container
*/
#define MLXSW_ITEM8(_type, _cname, _iname, _offset, _shift, _sizebits) \
static struct mlxsw_item __ITEM_NAME(_type, _cname, _iname) = { \
.offset = _offset, \
.shift = _shift, \
.size = {.bits = _sizebits,}, \
.name = #_type "_" #_cname "_" #_iname, \
}; \
static inline u8 mlxsw_##_type##_##_cname##_##_iname##_get(const char *buf) \
{ \
return __mlxsw_item_get8(buf, &__ITEM_NAME(_type, _cname, _iname), 0); \
} \
static inline void mlxsw_##_type##_##_cname##_##_iname##_set(char *buf, u8 val)\
{ \
__mlxsw_item_set8(buf, &__ITEM_NAME(_type, _cname, _iname), 0, val); \
}
#define MLXSW_ITEM8_INDEXED(_type, _cname, _iname, _offset, _shift, _sizebits, \
_step, _instepoffset, _norealshift) \
static struct mlxsw_item __ITEM_NAME(_type, _cname, _iname) = { \
.offset = _offset, \
.step = _step, \
.in_step_offset = _instepoffset, \
.shift = _shift, \
.no_real_shift = _norealshift, \
.size = {.bits = _sizebits,}, \
.name = #_type "_" #_cname "_" #_iname, \
}; \
static inline u8 \
mlxsw_##_type##_##_cname##_##_iname##_get(const char *buf, unsigned short index)\
{ \
return __mlxsw_item_get8(buf, &__ITEM_NAME(_type, _cname, _iname), \
index); \
} \
static inline void \
mlxsw_##_type##_##_cname##_##_iname##_set(char *buf, unsigned short index, \
u8 val) \
{ \
__mlxsw_item_set8(buf, &__ITEM_NAME(_type, _cname, _iname), \
index, val); \
}
#define MLXSW_ITEM16(_type, _cname, _iname, _offset, _shift, _sizebits) \
static struct mlxsw_item __ITEM_NAME(_type, _cname, _iname) = { \
.offset = _offset, \
......@@ -393,6 +476,11 @@ mlxsw_##_type##_##_cname##_##_iname##_memcpy_to(char *buf, const char *src) \
{ \
__mlxsw_item_memcpy_to(buf, src, \
&__ITEM_NAME(_type, _cname, _iname), 0); \
} \
static inline char * \
mlxsw_##_type##_##_cname##_##_iname##_data(char *buf) \
{ \
return __mlxsw_item_data(buf, &__ITEM_NAME(_type, _cname, _iname), 0); \
}
#define MLXSW_ITEM_BUF_INDEXED(_type, _cname, _iname, _offset, _sizebytes, \
......@@ -419,6 +507,12 @@ mlxsw_##_type##_##_cname##_##_iname##_memcpy_to(char *buf, \
{ \
__mlxsw_item_memcpy_to(buf, src, \
&__ITEM_NAME(_type, _cname, _iname), index); \
} \
static inline char * \
mlxsw_##_type##_##_cname##_##_iname##_data(char *buf, unsigned short index) \
{ \
return __mlxsw_item_data(buf, \
&__ITEM_NAME(_type, _cname, _iname), index); \
}
#define MLXSW_ITEM_BIT_ARRAY(_type, _cname, _iname, _offset, _sizebytes, \
......
/*
* drivers/net/ethernet/mellanox/mlxsw/reg.h
* Copyright (c) 2015 Mellanox Technologies. All rights reserved.
* Copyright (c) 2015-2017 Mellanox Technologies. All rights reserved.
* Copyright (c) 2015-2016 Ido Schimmel <idosch@mellanox.com>
* Copyright (c) 2015 Elad Raz <eladr@mellanox.com>
* Copyright (c) 2015-2016 Jiri Pirko <jiri@mellanox.com>
* Copyright (c) 2015-2017 Jiri Pirko <jiri@mellanox.com>
* Copyright (c) 2016 Yotam Gigi <yotamg@mellanox.com>
*
* Redistribution and use in source and binary forms, with or without
......@@ -1757,6 +1757,505 @@ static inline void mlxsw_reg_spvmlr_pack(char *payload, u8 local_port,
}
}
/* PPBT - Policy-Engine Port Binding Table
* ---------------------------------------
* This register is used for configuration of the Port Binding Table.
*/
#define MLXSW_REG_PPBT_ID 0x3002
#define MLXSW_REG_PPBT_LEN 0x14
MLXSW_REG_DEFINE(ppbt, MLXSW_REG_PPBT_ID, MLXSW_REG_PPBT_LEN);
enum mlxsw_reg_pxbt_e {
MLXSW_REG_PXBT_E_IACL,
MLXSW_REG_PXBT_E_EACL,
};
/* reg_ppbt_e
* Access: Index
*/
MLXSW_ITEM32(reg, ppbt, e, 0x00, 31, 1);
enum mlxsw_reg_pxbt_op {
MLXSW_REG_PXBT_OP_BIND,
MLXSW_REG_PXBT_OP_UNBIND,
};
/* reg_ppbt_op
* Access: RW
*/
MLXSW_ITEM32(reg, ppbt, op, 0x00, 28, 3);
/* reg_ppbt_local_port
* Local port. Not including CPU port.
* Access: Index
*/
MLXSW_ITEM32(reg, ppbt, local_port, 0x00, 16, 8);
/* reg_ppbt_g
* group - When set, the binding is of an ACL group. When cleared,
* the binding is of an ACL.
* Must be set to 1 for Spectrum.
* Access: RW
*/
MLXSW_ITEM32(reg, ppbt, g, 0x10, 31, 1);
/* reg_ppbt_acl_info
* ACL/ACL group identifier. If the g bit is set, this field should hold
* the acl_group_id, else it should hold the acl_id.
* Access: RW
*/
MLXSW_ITEM32(reg, ppbt, acl_info, 0x10, 0, 16);
static inline void mlxsw_reg_ppbt_pack(char *payload, enum mlxsw_reg_pxbt_e e,
enum mlxsw_reg_pxbt_op op,
u8 local_port, u16 acl_info)
{
MLXSW_REG_ZERO(ppbt, payload);
mlxsw_reg_ppbt_e_set(payload, e);
mlxsw_reg_ppbt_op_set(payload, op);
mlxsw_reg_ppbt_local_port_set(payload, local_port);
mlxsw_reg_ppbt_g_set(payload, true);
mlxsw_reg_ppbt_acl_info_set(payload, acl_info);
}
/* PACL - Policy-Engine ACL Register
* ---------------------------------
* This register is used for configuration of the ACL.
*/
#define MLXSW_REG_PACL_ID 0x3004
#define MLXSW_REG_PACL_LEN 0x70
MLXSW_REG_DEFINE(pacl, MLXSW_REG_PACL_ID, MLXSW_REG_PACL_LEN);
/* reg_pacl_v
* Valid. Setting the v bit makes the ACL valid. It should not be cleared
* while the ACL is bounded to either a port, VLAN or ACL rule.
* Access: RW
*/
MLXSW_ITEM32(reg, pacl, v, 0x00, 24, 1);
/* reg_pacl_acl_id
* An identifier representing the ACL (managed by software)
* Range 0 .. cap_max_acl_regions - 1
* Access: Index
*/
MLXSW_ITEM32(reg, pacl, acl_id, 0x08, 0, 16);
#define MLXSW_REG_PXXX_TCAM_REGION_INFO_LEN 16
/* reg_pacl_tcam_region_info
* Opaque object that represents a TCAM region.
* Obtained through PTAR register.
* Access: RW
*/
MLXSW_ITEM_BUF(reg, pacl, tcam_region_info, 0x30,
MLXSW_REG_PXXX_TCAM_REGION_INFO_LEN);
static inline void mlxsw_reg_pacl_pack(char *payload, u16 acl_id,
bool valid, const char *tcam_region_info)
{
MLXSW_REG_ZERO(pacl, payload);
mlxsw_reg_pacl_acl_id_set(payload, acl_id);
mlxsw_reg_pacl_v_set(payload, valid);
mlxsw_reg_pacl_tcam_region_info_memcpy_to(payload, tcam_region_info);
}
/* PAGT - Policy-Engine ACL Group Table
* ------------------------------------
* This register is used for configuration of the ACL Group Table.
*/
#define MLXSW_REG_PAGT_ID 0x3005
#define MLXSW_REG_PAGT_BASE_LEN 0x30
#define MLXSW_REG_PAGT_ACL_LEN 4
#define MLXSW_REG_PAGT_ACL_MAX_NUM 16
#define MLXSW_REG_PAGT_LEN (MLXSW_REG_PAGT_BASE_LEN + \
MLXSW_REG_PAGT_ACL_MAX_NUM * MLXSW_REG_PAGT_ACL_LEN)
MLXSW_REG_DEFINE(pagt, MLXSW_REG_PAGT_ID, MLXSW_REG_PAGT_LEN);
/* reg_pagt_size
* Number of ACLs in the group.
* Size 0 invalidates a group.
* Range 0 .. cap_max_acl_group_size (hard coded to 16 for now)
* Total number of ACLs in all groups must be lower or equal
* to cap_max_acl_tot_groups
* Note: a group which is binded must not be invalidated
* Access: Index
*/
MLXSW_ITEM32(reg, pagt, size, 0x00, 0, 8);
/* reg_pagt_acl_group_id
* An identifier (numbered from 0..cap_max_acl_groups-1) representing
* the ACL Group identifier (managed by software).
* Access: Index
*/
MLXSW_ITEM32(reg, pagt, acl_group_id, 0x08, 0, 16);
/* reg_pagt_acl_id
* ACL identifier
* Access: RW
*/
MLXSW_ITEM32_INDEXED(reg, pagt, acl_id, 0x30, 0, 16, 0x04, 0x00, false);
static inline void mlxsw_reg_pagt_pack(char *payload, u16 acl_group_id)
{
MLXSW_REG_ZERO(pagt, payload);
mlxsw_reg_pagt_acl_group_id_set(payload, acl_group_id);
}
static inline void mlxsw_reg_pagt_acl_id_pack(char *payload, int index,
u16 acl_id)
{
u8 size = mlxsw_reg_pagt_size_get(payload);
if (index >= size)
mlxsw_reg_pagt_size_set(payload, index + 1);
mlxsw_reg_pagt_acl_id_set(payload, index, acl_id);
}
/* PTAR - Policy-Engine TCAM Allocation Register
* ---------------------------------------------
* This register is used for allocation of regions in the TCAM.
* Note: Query method is not supported on this register.
*/
#define MLXSW_REG_PTAR_ID 0x3006
#define MLXSW_REG_PTAR_BASE_LEN 0x20
#define MLXSW_REG_PTAR_KEY_ID_LEN 1
#define MLXSW_REG_PTAR_KEY_ID_MAX_NUM 16
#define MLXSW_REG_PTAR_LEN (MLXSW_REG_PTAR_BASE_LEN + \
MLXSW_REG_PTAR_KEY_ID_MAX_NUM * MLXSW_REG_PTAR_KEY_ID_LEN)
MLXSW_REG_DEFINE(ptar, MLXSW_REG_PTAR_ID, MLXSW_REG_PTAR_LEN);
enum mlxsw_reg_ptar_op {
/* allocate a TCAM region */
MLXSW_REG_PTAR_OP_ALLOC,
/* resize a TCAM region */
MLXSW_REG_PTAR_OP_RESIZE,
/* deallocate TCAM region */
MLXSW_REG_PTAR_OP_FREE,
/* test allocation */
MLXSW_REG_PTAR_OP_TEST,
};
/* reg_ptar_op
* Access: OP
*/
MLXSW_ITEM32(reg, ptar, op, 0x00, 28, 4);
/* reg_ptar_action_set_type
* Type of action set to be used on this region.
* For Spectrum, this is always type 2 - "flexible"
* Access: WO
*/
MLXSW_ITEM32(reg, ptar, action_set_type, 0x00, 16, 8);
/* reg_ptar_key_type
* TCAM key type for the region.
* For Spectrum, this is always type 0x50 - "FLEX_KEY"
* Access: WO
*/
MLXSW_ITEM32(reg, ptar, key_type, 0x00, 0, 8);
/* reg_ptar_region_size
* TCAM region size. When allocating/resizing this is the requested size,
* the response is the actual size. Note that actual size may be
* larger than requested.
* Allowed range 1 .. cap_max_rules-1
* Reserved during op deallocate.
* Access: WO
*/
MLXSW_ITEM32(reg, ptar, region_size, 0x04, 0, 16);
/* reg_ptar_region_id
* Region identifier
* Range 0 .. cap_max_regions-1
* Access: Index
*/
MLXSW_ITEM32(reg, ptar, region_id, 0x08, 0, 16);
/* reg_ptar_tcam_region_info
* Opaque object that represents the TCAM region.
* Returned when allocating a region.
* Provided by software for ACL generation and region deallocation and resize.
* Access: RW
*/
MLXSW_ITEM_BUF(reg, ptar, tcam_region_info, 0x10,
MLXSW_REG_PXXX_TCAM_REGION_INFO_LEN);
/* reg_ptar_flexible_key_id
* Identifier of the Flexible Key.
* Only valid if key_type == "FLEX_KEY"
* The key size will be rounded up to one of the following values:
* 9B, 18B, 36B, 54B.
* This field is reserved for in resize operation.
* Access: WO
*/
MLXSW_ITEM8_INDEXED(reg, ptar, flexible_key_id, 0x20, 0, 8,
MLXSW_REG_PTAR_KEY_ID_LEN, 0x00, false);
static inline void mlxsw_reg_ptar_pack(char *payload, enum mlxsw_reg_ptar_op op,
u16 region_size, u16 region_id,
const char *tcam_region_info)
{
MLXSW_REG_ZERO(ptar, payload);
mlxsw_reg_ptar_op_set(payload, op);
mlxsw_reg_ptar_action_set_type_set(payload, 2); /* "flexible" */
mlxsw_reg_ptar_key_type_set(payload, 0x50); /* "FLEX_KEY" */
mlxsw_reg_ptar_region_size_set(payload, region_size);
mlxsw_reg_ptar_region_id_set(payload, region_id);
mlxsw_reg_ptar_tcam_region_info_memcpy_to(payload, tcam_region_info);
}
static inline void mlxsw_reg_ptar_key_id_pack(char *payload, int index,
u16 key_id)
{
mlxsw_reg_ptar_flexible_key_id_set(payload, index, key_id);
}
static inline void mlxsw_reg_ptar_unpack(char *payload, char *tcam_region_info)
{
mlxsw_reg_ptar_tcam_region_info_memcpy_from(payload, tcam_region_info);
}
/* PPBS - Policy-Engine Policy Based Switching Register
* ----------------------------------------------------
* This register retrieves and sets Policy Based Switching Table entries.
*/
#define MLXSW_REG_PPBS_ID 0x300C
#define MLXSW_REG_PPBS_LEN 0x14
MLXSW_REG_DEFINE(ppbs, MLXSW_REG_PPBS_ID, MLXSW_REG_PPBS_LEN);
/* reg_ppbs_pbs_ptr
* Index into the PBS table.
* For Spectrum, the index points to the KVD Linear.
* Access: Index
*/
MLXSW_ITEM32(reg, ppbs, pbs_ptr, 0x08, 0, 24);
/* reg_ppbs_system_port
* Unique port identifier for the final destination of the packet.
* Access: RW
*/
MLXSW_ITEM32(reg, ppbs, system_port, 0x10, 0, 16);
static inline void mlxsw_reg_ppbs_pack(char *payload, u32 pbs_ptr,
u16 system_port)
{
MLXSW_REG_ZERO(ppbs, payload);
mlxsw_reg_ppbs_pbs_ptr_set(payload, pbs_ptr);
mlxsw_reg_ppbs_system_port_set(payload, system_port);
}
/* PRCR - Policy-Engine Rules Copy Register
* ----------------------------------------
* This register is used for accessing rules within a TCAM region.
*/
#define MLXSW_REG_PRCR_ID 0x300D
#define MLXSW_REG_PRCR_LEN 0x40
MLXSW_REG_DEFINE(prcr, MLXSW_REG_PRCR_ID, MLXSW_REG_PRCR_LEN);
enum mlxsw_reg_prcr_op {
/* Move rules. Moves the rules from "tcam_region_info" starting
* at offset "offset" to "dest_tcam_region_info"
* at offset "dest_offset."
*/
MLXSW_REG_PRCR_OP_MOVE,
/* Copy rules. Copies the rules from "tcam_region_info" starting
* at offset "offset" to "dest_tcam_region_info"
* at offset "dest_offset."
*/
MLXSW_REG_PRCR_OP_COPY,
};
/* reg_prcr_op
* Access: OP
*/
MLXSW_ITEM32(reg, prcr, op, 0x00, 28, 4);
/* reg_prcr_offset
* Offset within the source region to copy/move from.
* Access: Index
*/
MLXSW_ITEM32(reg, prcr, offset, 0x00, 0, 16);
/* reg_prcr_size
* The number of rules to copy/move.
* Access: WO
*/
MLXSW_ITEM32(reg, prcr, size, 0x04, 0, 16);
/* reg_prcr_tcam_region_info
* Opaque object that represents the source TCAM region.
* Access: Index
*/
MLXSW_ITEM_BUF(reg, prcr, tcam_region_info, 0x10,
MLXSW_REG_PXXX_TCAM_REGION_INFO_LEN);
/* reg_prcr_dest_offset
* Offset within the source region to copy/move to.
* Access: Index
*/
MLXSW_ITEM32(reg, prcr, dest_offset, 0x20, 0, 16);
/* reg_prcr_dest_tcam_region_info
* Opaque object that represents the destination TCAM region.
* Access: Index
*/
MLXSW_ITEM_BUF(reg, prcr, dest_tcam_region_info, 0x30,
MLXSW_REG_PXXX_TCAM_REGION_INFO_LEN);
static inline void mlxsw_reg_prcr_pack(char *payload, enum mlxsw_reg_prcr_op op,
const char *src_tcam_region_info,
u16 src_offset,
const char *dest_tcam_region_info,
u16 dest_offset, u16 size)
{
MLXSW_REG_ZERO(prcr, payload);
mlxsw_reg_prcr_op_set(payload, op);
mlxsw_reg_prcr_offset_set(payload, src_offset);
mlxsw_reg_prcr_size_set(payload, size);
mlxsw_reg_prcr_tcam_region_info_memcpy_to(payload,
src_tcam_region_info);
mlxsw_reg_prcr_dest_offset_set(payload, dest_offset);
mlxsw_reg_prcr_dest_tcam_region_info_memcpy_to(payload,
dest_tcam_region_info);
}
/* PEFA - Policy-Engine Extended Flexible Action Register
* ------------------------------------------------------
* This register is used for accessing an extended flexible action entry
* in the central KVD Linear Database.
*/
#define MLXSW_REG_PEFA_ID 0x300F
#define MLXSW_REG_PEFA_LEN 0xB0
MLXSW_REG_DEFINE(pefa, MLXSW_REG_PEFA_ID, MLXSW_REG_PEFA_LEN);
/* reg_pefa_index
* Index in the KVD Linear Centralized Database.
* Access: Index
*/
MLXSW_ITEM32(reg, pefa, index, 0x00, 0, 24);
#define MLXSW_REG_PXXX_FLEX_ACTION_SET_LEN 0xA8
/* reg_pefa_flex_action_set
* Action-set to perform when rule is matched.
* Must be zero padded if action set is shorter.
* Access: RW
*/
MLXSW_ITEM_BUF(reg, pefa, flex_action_set, 0x08,
MLXSW_REG_PXXX_FLEX_ACTION_SET_LEN);
static inline void mlxsw_reg_pefa_pack(char *payload, u32 index,
const char *flex_action_set)
{
MLXSW_REG_ZERO(pefa, payload);
mlxsw_reg_pefa_index_set(payload, index);
mlxsw_reg_pefa_flex_action_set_memcpy_to(payload, flex_action_set);
}
/* PTCE-V2 - Policy-Engine TCAM Entry Register Version 2
* -----------------------------------------------------
* This register is used for accessing rules within a TCAM region.
* It is a new version of PTCE in order to support wider key,
* mask and action within a TCAM region. This register is not supported
* by SwitchX and SwitchX-2.
*/
#define MLXSW_REG_PTCE2_ID 0x3017
#define MLXSW_REG_PTCE2_LEN 0x1D8
MLXSW_REG_DEFINE(ptce2, MLXSW_REG_PTCE2_ID, MLXSW_REG_PTCE2_LEN);
/* reg_ptce2_v
* Valid.
* Access: RW
*/
MLXSW_ITEM32(reg, ptce2, v, 0x00, 31, 1);
/* reg_ptce2_a
* Activity. Set if a packet lookup has hit on the specific entry.
* To clear the "a" bit, use "clear activity" op or "clear on read" op.
* Access: RO
*/
MLXSW_ITEM32(reg, ptce2, a, 0x00, 30, 1);
enum mlxsw_reg_ptce2_op {
/* Read operation. */
MLXSW_REG_PTCE2_OP_QUERY_READ = 0,
/* clear on read operation. Used to read entry
* and clear Activity bit.
*/
MLXSW_REG_PTCE2_OP_QUERY_CLEAR_ON_READ = 1,
/* Write operation. Used to write a new entry to the table.
* All R/W fields are relevant for new entry. Activity bit is set
* for new entries - Note write with v = 0 will delete the entry.
*/
MLXSW_REG_PTCE2_OP_WRITE_WRITE = 0,
/* Update action. Only action set will be updated. */
MLXSW_REG_PTCE2_OP_WRITE_UPDATE = 1,
/* Clear activity. A bit is cleared for the entry. */
MLXSW_REG_PTCE2_OP_WRITE_CLEAR_ACTIVITY = 2,
};
/* reg_ptce2_op
* Access: OP
*/
MLXSW_ITEM32(reg, ptce2, op, 0x00, 20, 3);
/* reg_ptce2_offset
* Access: Index
*/
MLXSW_ITEM32(reg, ptce2, offset, 0x00, 0, 16);
/* reg_ptce2_tcam_region_info
* Opaque object that represents the TCAM region.
* Access: Index
*/
MLXSW_ITEM_BUF(reg, ptce2, tcam_region_info, 0x10,
MLXSW_REG_PXXX_TCAM_REGION_INFO_LEN);
#define MLXSW_REG_PTCE2_FLEX_KEY_BLOCKS_LEN 96
/* reg_ptce2_flex_key_blocks
* ACL Key.
* Access: RW
*/
MLXSW_ITEM_BUF(reg, ptce2, flex_key_blocks, 0x20,
MLXSW_REG_PTCE2_FLEX_KEY_BLOCKS_LEN);
/* reg_ptce2_mask
* mask- in the same size as key. A bit that is set directs the TCAM
* to compare the corresponding bit in key. A bit that is clear directs
* the TCAM to ignore the corresponding bit in key.
* Access: RW
*/
MLXSW_ITEM_BUF(reg, ptce2, mask, 0x80,
MLXSW_REG_PTCE2_FLEX_KEY_BLOCKS_LEN);
/* reg_ptce2_flex_action_set
* ACL action set.
* Access: RW
*/
MLXSW_ITEM_BUF(reg, ptce2, flex_action_set, 0xE0,
MLXSW_REG_PXXX_FLEX_ACTION_SET_LEN);
static inline void mlxsw_reg_ptce2_pack(char *payload, bool valid,
enum mlxsw_reg_ptce2_op op,
const char *tcam_region_info,
u16 offset)
{
MLXSW_REG_ZERO(ptce2, payload);
mlxsw_reg_ptce2_v_set(payload, valid);
mlxsw_reg_ptce2_op_set(payload, op);
mlxsw_reg_ptce2_offset_set(payload, offset);
mlxsw_reg_ptce2_tcam_region_info_memcpy_to(payload, tcam_region_info);
}
/* QPCR - QoS Policer Configuration Register
* -----------------------------------------
* The QPCR register is used to create policers - that limit
......@@ -5434,6 +5933,14 @@ static const struct mlxsw_reg_info *mlxsw_reg_infos[] = {
MLXSW_REG(svpe),
MLXSW_REG(sfmr),
MLXSW_REG(spvmlr),
MLXSW_REG(ppbt),
MLXSW_REG(pacl),
MLXSW_REG(pagt),
MLXSW_REG(ptar),
MLXSW_REG(ppbs),
MLXSW_REG(prcr),
MLXSW_REG(pefa),
MLXSW_REG(ptce2),
MLXSW_REG(qpcr),
MLXSW_REG(qtct),
MLXSW_REG(qeec),
......
/*
* drivers/net/ethernet/mellanox/mlxsw/resources.h
* Copyright (c) 2016 Mellanox Technologies. All rights reserved.
* Copyright (c) 2016 Jiri Pirko <jiri@mellanox.com>
* Copyright (c) 2016-2017 Mellanox Technologies. All rights reserved.
* Copyright (c) 2016-2017 Jiri Pirko <jiri@mellanox.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
......@@ -48,6 +48,14 @@ enum mlxsw_res_id {
MLXSW_RES_ID_MAX_LAG,
MLXSW_RES_ID_MAX_LAG_MEMBERS,
MLXSW_RES_ID_MAX_BUFFER_SIZE,
MLXSW_RES_ID_ACL_MAX_TCAM_REGIONS,
MLXSW_RES_ID_ACL_MAX_TCAM_RULES,
MLXSW_RES_ID_ACL_MAX_REGIONS,
MLXSW_RES_ID_ACL_MAX_GROUPS,
MLXSW_RES_ID_ACL_MAX_GROUP_SIZE,
MLXSW_RES_ID_ACL_FLEX_KEYS,
MLXSW_RES_ID_ACL_MAX_ACTION_PER_RULE,
MLXSW_RES_ID_ACL_ACTIONS_PER_SET,
MLXSW_RES_ID_MAX_CPU_POLICERS,
MLXSW_RES_ID_MAX_VRS,
MLXSW_RES_ID_MAX_RIFS,
......@@ -72,6 +80,14 @@ static u16 mlxsw_res_ids[] = {
[MLXSW_RES_ID_MAX_LAG] = 0x2520,
[MLXSW_RES_ID_MAX_LAG_MEMBERS] = 0x2521,
[MLXSW_RES_ID_MAX_BUFFER_SIZE] = 0x2802, /* Bytes */
[MLXSW_RES_ID_ACL_MAX_TCAM_REGIONS] = 0x2901,
[MLXSW_RES_ID_ACL_MAX_TCAM_RULES] = 0x2902,
[MLXSW_RES_ID_ACL_MAX_REGIONS] = 0x2903,
[MLXSW_RES_ID_ACL_MAX_GROUPS] = 0x2904,
[MLXSW_RES_ID_ACL_MAX_GROUP_SIZE] = 0x2905,
[MLXSW_RES_ID_ACL_FLEX_KEYS] = 0x2910,
[MLXSW_RES_ID_ACL_MAX_ACTION_PER_RULE] = 0x2911,
[MLXSW_RES_ID_ACL_ACTIONS_PER_SET] = 0x2912,
[MLXSW_RES_ID_MAX_CPU_POLICERS] = 0x2A13,
[MLXSW_RES_ID_MAX_VRS] = 0x2C01,
[MLXSW_RES_ID_MAX_RIFS] = 0x2C02,
......
/*
* drivers/net/ethernet/mellanox/mlxsw/spectrum.c
* Copyright (c) 2015 Mellanox Technologies. All rights reserved.
* Copyright (c) 2015 Jiri Pirko <jiri@mellanox.com>
* Copyright (c) 2015-2017 Mellanox Technologies. All rights reserved.
* Copyright (c) 2015-2017 Jiri Pirko <jiri@mellanox.com>
* Copyright (c) 2015 Ido Schimmel <idosch@mellanox.com>
* Copyright (c) 2015 Elad Raz <eladr@mellanox.com>
*
......@@ -138,8 +138,6 @@ MLXSW_ITEM32(tx, hdr, fid, 0x08, 0, 16);
*/
MLXSW_ITEM32(tx, hdr, type, 0x0C, 0, 4);
static bool mlxsw_sp_port_dev_check(const struct net_device *dev);
static void mlxsw_sp_txhdr_construct(struct sk_buff *skb,
const struct mlxsw_tx_info *tx_info)
{
......@@ -1357,7 +1355,8 @@ static int mlxsw_sp_setup_tc(struct net_device *dev, u32 handle,
struct mlxsw_sp_port *mlxsw_sp_port = netdev_priv(dev);
bool ingress = TC_H_MAJ(handle) == TC_H_MAJ(TC_H_INGRESS);
if (tc->type == TC_SETUP_MATCHALL) {
switch (tc->type) {
case TC_SETUP_MATCHALL:
switch (tc->cls_mall->command) {
case TC_CLSMATCHALL_REPLACE:
return mlxsw_sp_port_add_cls_matchall(mlxsw_sp_port,
......@@ -1371,6 +1370,18 @@ static int mlxsw_sp_setup_tc(struct net_device *dev, u32 handle,
default:
return -EINVAL;
}
case TC_SETUP_CLSFLOWER:
switch (tc->cls_flower->command) {
case TC_CLSFLOWER_REPLACE:
return mlxsw_sp_flower_replace(mlxsw_sp_port, ingress,
proto, tc->cls_flower);
case TC_CLSFLOWER_DESTROY:
mlxsw_sp_flower_destroy(mlxsw_sp_port, ingress,
tc->cls_flower);
return 0;
default:
return -EOPNOTSUPP;
}
}
return -EOPNOTSUPP;
......@@ -3203,6 +3214,12 @@ static int mlxsw_sp_init(struct mlxsw_core *mlxsw_core,
goto err_span_init;
}
err = mlxsw_sp_acl_init(mlxsw_sp);
if (err) {
dev_err(mlxsw_sp->bus_info->dev, "Failed to initialize ACL\n");
goto err_acl_init;
}
err = mlxsw_sp_ports_create(mlxsw_sp);
if (err) {
dev_err(mlxsw_sp->bus_info->dev, "Failed to create ports\n");
......@@ -3212,6 +3229,8 @@ static int mlxsw_sp_init(struct mlxsw_core *mlxsw_core,
return 0;
err_ports_create:
mlxsw_sp_acl_fini(mlxsw_sp);
err_acl_init:
mlxsw_sp_span_fini(mlxsw_sp);
err_span_init:
mlxsw_sp_router_fini(mlxsw_sp);
......@@ -3232,6 +3251,7 @@ static void mlxsw_sp_fini(struct mlxsw_core *mlxsw_core)
struct mlxsw_sp *mlxsw_sp = mlxsw_core_driver_priv(mlxsw_core);
mlxsw_sp_ports_remove(mlxsw_sp);
mlxsw_sp_acl_fini(mlxsw_sp);
mlxsw_sp_span_fini(mlxsw_sp);
mlxsw_sp_router_fini(mlxsw_sp);
mlxsw_sp_switchdev_fini(mlxsw_sp);
......@@ -3297,7 +3317,7 @@ static struct mlxsw_driver mlxsw_sp_driver = {
.profile = &mlxsw_sp_config_profile,
};
static bool mlxsw_sp_port_dev_check(const struct net_device *dev)
bool mlxsw_sp_port_dev_check(const struct net_device *dev)
{
return dev->netdev_ops == &mlxsw_sp_port_netdev_ops;
}
......
/*
* drivers/net/ethernet/mellanox/mlxsw/spectrum.h
* Copyright (c) 2015 Mellanox Technologies. All rights reserved.
* Copyright (c) 2015 Jiri Pirko <jiri@mellanox.com>
* Copyright (c) 2015-2017 Mellanox Technologies. All rights reserved.
* Copyright (c) 2015-2017 Jiri Pirko <jiri@mellanox.com>
* Copyright (c) 2015 Ido Schimmel <idosch@mellanox.com>
* Copyright (c) 2015 Elad Raz <eladr@mellanox.com>
*
......@@ -47,9 +47,12 @@
#include <linux/in6.h>
#include <linux/notifier.h>
#include <net/psample.h>
#include <net/pkt_cls.h>
#include "port.h"
#include "core.h"
#include "core_acl_flex_keys.h"
#include "core_acl_flex_actions.h"
#define MLXSW_SP_VFID_BASE VLAN_N_VID
#define MLXSW_SP_VFID_MAX 6656 /* Bridged VLAN interfaces */
......@@ -262,6 +265,8 @@ struct mlxsw_sp_router {
bool aborted;
};
struct mlxsw_sp_acl;
struct mlxsw_sp {
struct {
struct list_head list;
......@@ -291,6 +296,7 @@ struct mlxsw_sp {
u8 port_to_module[MLXSW_PORT_MAX_PORTS];
struct mlxsw_sp_sb sb;
struct mlxsw_sp_router router;
struct mlxsw_sp_acl *acl;
struct {
DECLARE_BITMAP(usage, MLXSW_SP_KVD_LINEAR_SIZE);
} kvdl;
......@@ -373,6 +379,7 @@ struct mlxsw_sp_port {
struct mlxsw_sp_port_sample *sample;
};
bool mlxsw_sp_port_dev_check(const struct net_device *dev);
struct mlxsw_sp_port *mlxsw_sp_port_lower_dev_hold(struct net_device *dev);
void mlxsw_sp_port_dev_put(struct mlxsw_sp_port *mlxsw_sp_port);
......@@ -602,4 +609,99 @@ int mlxsw_sp_router_netevent_event(struct notifier_block *unused,
int mlxsw_sp_kvdl_alloc(struct mlxsw_sp *mlxsw_sp, unsigned int entry_count);
void mlxsw_sp_kvdl_free(struct mlxsw_sp *mlxsw_sp, int entry_index);
struct mlxsw_afk *mlxsw_sp_acl_afk(struct mlxsw_sp_acl *acl);
struct mlxsw_sp_acl_rule_info {
unsigned int priority;
struct mlxsw_afk_element_values values;
struct mlxsw_afa_block *act_block;
};
enum mlxsw_sp_acl_profile {
MLXSW_SP_ACL_PROFILE_FLOWER,
};
struct mlxsw_sp_acl_profile_ops {
size_t ruleset_priv_size;
int (*ruleset_add)(struct mlxsw_sp *mlxsw_sp,
void *priv, void *ruleset_priv);
void (*ruleset_del)(struct mlxsw_sp *mlxsw_sp, void *ruleset_priv);
int (*ruleset_bind)(struct mlxsw_sp *mlxsw_sp, void *ruleset_priv,
struct net_device *dev, bool ingress);
void (*ruleset_unbind)(struct mlxsw_sp *mlxsw_sp, void *ruleset_priv);
size_t rule_priv_size;
int (*rule_add)(struct mlxsw_sp *mlxsw_sp,
void *ruleset_priv, void *rule_priv,
struct mlxsw_sp_acl_rule_info *rulei);
void (*rule_del)(struct mlxsw_sp *mlxsw_sp, void *rule_priv);
};
struct mlxsw_sp_acl_ops {
size_t priv_size;
int (*init)(struct mlxsw_sp *mlxsw_sp, void *priv);
void (*fini)(struct mlxsw_sp *mlxsw_sp, void *priv);
const struct mlxsw_sp_acl_profile_ops *
(*profile_ops)(struct mlxsw_sp *mlxsw_sp,
enum mlxsw_sp_acl_profile profile);
};
struct mlxsw_sp_acl_ruleset;
struct mlxsw_sp_acl_ruleset *
mlxsw_sp_acl_ruleset_get(struct mlxsw_sp *mlxsw_sp,
struct net_device *dev, bool ingress,
enum mlxsw_sp_acl_profile profile);
void mlxsw_sp_acl_ruleset_put(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_ruleset *ruleset);
struct mlxsw_sp_acl_rule_info *
mlxsw_sp_acl_rulei_create(struct mlxsw_sp_acl *acl);
void mlxsw_sp_acl_rulei_destroy(struct mlxsw_sp_acl_rule_info *rulei);
int mlxsw_sp_acl_rulei_commit(struct mlxsw_sp_acl_rule_info *rulei);
void mlxsw_sp_acl_rulei_priority(struct mlxsw_sp_acl_rule_info *rulei,
unsigned int priority);
void mlxsw_sp_acl_rulei_keymask_u32(struct mlxsw_sp_acl_rule_info *rulei,
enum mlxsw_afk_element element,
u32 key_value, u32 mask_value);
void mlxsw_sp_acl_rulei_keymask_buf(struct mlxsw_sp_acl_rule_info *rulei,
enum mlxsw_afk_element element,
const char *key_value,
const char *mask_value, unsigned int len);
void mlxsw_sp_acl_rulei_act_continue(struct mlxsw_sp_acl_rule_info *rulei);
void mlxsw_sp_acl_rulei_act_jump(struct mlxsw_sp_acl_rule_info *rulei,
u16 group_id);
int mlxsw_sp_acl_rulei_act_drop(struct mlxsw_sp_acl_rule_info *rulei);
int mlxsw_sp_acl_rulei_act_fwd(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_rule_info *rulei,
struct net_device *out_dev);
struct mlxsw_sp_acl_rule;
struct mlxsw_sp_acl_rule *
mlxsw_sp_acl_rule_create(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_ruleset *ruleset,
unsigned long cookie);
void mlxsw_sp_acl_rule_destroy(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_rule *rule);
int mlxsw_sp_acl_rule_add(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_rule *rule);
void mlxsw_sp_acl_rule_del(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_rule *rule);
struct mlxsw_sp_acl_rule *
mlxsw_sp_acl_rule_lookup(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_ruleset *ruleset,
unsigned long cookie);
struct mlxsw_sp_acl_rule_info *
mlxsw_sp_acl_rule_rulei(struct mlxsw_sp_acl_rule *rule);
int mlxsw_sp_acl_init(struct mlxsw_sp *mlxsw_sp);
void mlxsw_sp_acl_fini(struct mlxsw_sp *mlxsw_sp);
extern const struct mlxsw_sp_acl_ops mlxsw_sp_acl_tcam_ops;
int mlxsw_sp_flower_replace(struct mlxsw_sp_port *mlxsw_sp_port, bool ingress,
__be16 protocol, struct tc_cls_flower_offload *f);
void mlxsw_sp_flower_destroy(struct mlxsw_sp_port *mlxsw_sp_port, bool ingress,
struct tc_cls_flower_offload *f);
#endif
/*
* drivers/net/ethernet/mellanox/mlxsw/spectrum_acl.c
* Copyright (c) 2017 Mellanox Technologies. All rights reserved.
* Copyright (c) 2017 Jiri Pirko <jiri@mellanox.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
* 3. Neither the names of the copyright holders nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* Alternatively, this software may be distributed under the terms of the
* GNU General Public License ("GPL") version 2 as published by the Free
* Software Foundation.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/list.h>
#include <linux/string.h>
#include <linux/rhashtable.h>
#include <linux/netdevice.h>
#include "reg.h"
#include "core.h"
#include "resources.h"
#include "spectrum.h"
#include "core_acl_flex_keys.h"
#include "core_acl_flex_actions.h"
#include "spectrum_acl_flex_keys.h"
struct mlxsw_sp_acl {
struct mlxsw_afk *afk;
struct mlxsw_afa *afa;
const struct mlxsw_sp_acl_ops *ops;
struct rhashtable ruleset_ht;
unsigned long priv[0];
/* priv has to be always the last item */
};
struct mlxsw_afk *mlxsw_sp_acl_afk(struct mlxsw_sp_acl *acl)
{
return acl->afk;
}
struct mlxsw_sp_acl_ruleset_ht_key {
struct net_device *dev; /* dev this ruleset is bound to */
bool ingress;
const struct mlxsw_sp_acl_profile_ops *ops;
};
struct mlxsw_sp_acl_ruleset {
struct rhash_head ht_node; /* Member of acl HT */
struct mlxsw_sp_acl_ruleset_ht_key ht_key;
struct rhashtable rule_ht;
unsigned int ref_count;
unsigned long priv[0];
/* priv has to be always the last item */
};
struct mlxsw_sp_acl_rule {
struct rhash_head ht_node; /* Member of rule HT */
unsigned long cookie; /* HT key */
struct mlxsw_sp_acl_ruleset *ruleset;
struct mlxsw_sp_acl_rule_info *rulei;
unsigned long priv[0];
/* priv has to be always the last item */
};
static const struct rhashtable_params mlxsw_sp_acl_ruleset_ht_params = {
.key_len = sizeof(struct mlxsw_sp_acl_ruleset_ht_key),
.key_offset = offsetof(struct mlxsw_sp_acl_ruleset, ht_key),
.head_offset = offsetof(struct mlxsw_sp_acl_ruleset, ht_node),
.automatic_shrinking = true,
};
static const struct rhashtable_params mlxsw_sp_acl_rule_ht_params = {
.key_len = sizeof(unsigned long),
.key_offset = offsetof(struct mlxsw_sp_acl_rule, cookie),
.head_offset = offsetof(struct mlxsw_sp_acl_rule, ht_node),
.automatic_shrinking = true,
};
static struct mlxsw_sp_acl_ruleset *
mlxsw_sp_acl_ruleset_create(struct mlxsw_sp *mlxsw_sp,
const struct mlxsw_sp_acl_profile_ops *ops)
{
struct mlxsw_sp_acl *acl = mlxsw_sp->acl;
struct mlxsw_sp_acl_ruleset *ruleset;
size_t alloc_size;
int err;
alloc_size = sizeof(*ruleset) + ops->ruleset_priv_size;
ruleset = kzalloc(alloc_size, GFP_KERNEL);
if (!ruleset)
return ERR_PTR(-ENOMEM);
ruleset->ref_count = 1;
ruleset->ht_key.ops = ops;
err = rhashtable_init(&ruleset->rule_ht, &mlxsw_sp_acl_rule_ht_params);
if (err)
goto err_rhashtable_init;
err = ops->ruleset_add(mlxsw_sp, acl->priv, ruleset->priv);
if (err)
goto err_ops_ruleset_add;
return ruleset;
err_ops_ruleset_add:
rhashtable_destroy(&ruleset->rule_ht);
err_rhashtable_init:
kfree(ruleset);
return ERR_PTR(err);
}
static void mlxsw_sp_acl_ruleset_destroy(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_ruleset *ruleset)
{
const struct mlxsw_sp_acl_profile_ops *ops = ruleset->ht_key.ops;
ops->ruleset_del(mlxsw_sp, ruleset->priv);
rhashtable_destroy(&ruleset->rule_ht);
kfree(ruleset);
}
static int mlxsw_sp_acl_ruleset_bind(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_ruleset *ruleset,
struct net_device *dev, bool ingress)
{
const struct mlxsw_sp_acl_profile_ops *ops = ruleset->ht_key.ops;
struct mlxsw_sp_acl *acl = mlxsw_sp->acl;
int err;
ruleset->ht_key.dev = dev;
ruleset->ht_key.ingress = ingress;
err = rhashtable_insert_fast(&acl->ruleset_ht, &ruleset->ht_node,
mlxsw_sp_acl_ruleset_ht_params);
if (err)
return err;
err = ops->ruleset_bind(mlxsw_sp, ruleset->priv, dev, ingress);
if (err)
goto err_ops_ruleset_bind;
return 0;
err_ops_ruleset_bind:
rhashtable_remove_fast(&acl->ruleset_ht, &ruleset->ht_node,
mlxsw_sp_acl_ruleset_ht_params);
return err;
}
static void mlxsw_sp_acl_ruleset_unbind(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_ruleset *ruleset)
{
const struct mlxsw_sp_acl_profile_ops *ops = ruleset->ht_key.ops;
struct mlxsw_sp_acl *acl = mlxsw_sp->acl;
ops->ruleset_unbind(mlxsw_sp, ruleset->priv);
rhashtable_remove_fast(&acl->ruleset_ht, &ruleset->ht_node,
mlxsw_sp_acl_ruleset_ht_params);
}
static void mlxsw_sp_acl_ruleset_ref_inc(struct mlxsw_sp_acl_ruleset *ruleset)
{
ruleset->ref_count++;
}
static void mlxsw_sp_acl_ruleset_ref_dec(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_ruleset *ruleset)
{
if (--ruleset->ref_count)
return;
mlxsw_sp_acl_ruleset_unbind(mlxsw_sp, ruleset);
mlxsw_sp_acl_ruleset_destroy(mlxsw_sp, ruleset);
}
struct mlxsw_sp_acl_ruleset *
mlxsw_sp_acl_ruleset_get(struct mlxsw_sp *mlxsw_sp,
struct net_device *dev, bool ingress,
enum mlxsw_sp_acl_profile profile)
{
const struct mlxsw_sp_acl_profile_ops *ops;
struct mlxsw_sp_acl *acl = mlxsw_sp->acl;
struct mlxsw_sp_acl_ruleset_ht_key ht_key;
struct mlxsw_sp_acl_ruleset *ruleset;
int err;
ops = acl->ops->profile_ops(mlxsw_sp, profile);
if (!ops)
return ERR_PTR(-EINVAL);
memset(&ht_key, 0, sizeof(ht_key));
ht_key.dev = dev;
ht_key.ingress = ingress;
ht_key.ops = ops;
ruleset = rhashtable_lookup_fast(&acl->ruleset_ht, &ht_key,
mlxsw_sp_acl_ruleset_ht_params);
if (ruleset) {
mlxsw_sp_acl_ruleset_ref_inc(ruleset);
return ruleset;
}
ruleset = mlxsw_sp_acl_ruleset_create(mlxsw_sp, ops);
if (IS_ERR(ruleset))
return ruleset;
err = mlxsw_sp_acl_ruleset_bind(mlxsw_sp, ruleset, dev, ingress);
if (err)
goto err_ruleset_bind;
return ruleset;
err_ruleset_bind:
mlxsw_sp_acl_ruleset_destroy(mlxsw_sp, ruleset);
return ERR_PTR(err);
}
void mlxsw_sp_acl_ruleset_put(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_ruleset *ruleset)
{
mlxsw_sp_acl_ruleset_ref_dec(mlxsw_sp, ruleset);
}
struct mlxsw_sp_acl_rule_info *
mlxsw_sp_acl_rulei_create(struct mlxsw_sp_acl *acl)
{
struct mlxsw_sp_acl_rule_info *rulei;
int err;
rulei = kzalloc(sizeof(*rulei), GFP_KERNEL);
if (!rulei)
return NULL;
rulei->act_block = mlxsw_afa_block_create(acl->afa);
if (IS_ERR(rulei->act_block)) {
err = PTR_ERR(rulei->act_block);
goto err_afa_block_create;
}
return rulei;
err_afa_block_create:
kfree(rulei);
return ERR_PTR(err);
}
void mlxsw_sp_acl_rulei_destroy(struct mlxsw_sp_acl_rule_info *rulei)
{
mlxsw_afa_block_destroy(rulei->act_block);
kfree(rulei);
}
int mlxsw_sp_acl_rulei_commit(struct mlxsw_sp_acl_rule_info *rulei)
{
return mlxsw_afa_block_commit(rulei->act_block);
}
void mlxsw_sp_acl_rulei_priority(struct mlxsw_sp_acl_rule_info *rulei,
unsigned int priority)
{
rulei->priority = priority;
}
void mlxsw_sp_acl_rulei_keymask_u32(struct mlxsw_sp_acl_rule_info *rulei,
enum mlxsw_afk_element element,
u32 key_value, u32 mask_value)
{
mlxsw_afk_values_add_u32(&rulei->values, element,
key_value, mask_value);
}
void mlxsw_sp_acl_rulei_keymask_buf(struct mlxsw_sp_acl_rule_info *rulei,
enum mlxsw_afk_element element,
const char *key_value,
const char *mask_value, unsigned int len)
{
mlxsw_afk_values_add_buf(&rulei->values, element,
key_value, mask_value, len);
}
void mlxsw_sp_acl_rulei_act_continue(struct mlxsw_sp_acl_rule_info *rulei)
{
mlxsw_afa_block_continue(rulei->act_block);
}
void mlxsw_sp_acl_rulei_act_jump(struct mlxsw_sp_acl_rule_info *rulei,
u16 group_id)
{
mlxsw_afa_block_jump(rulei->act_block, group_id);
}
int mlxsw_sp_acl_rulei_act_drop(struct mlxsw_sp_acl_rule_info *rulei)
{
return mlxsw_afa_block_append_drop(rulei->act_block);
}
int mlxsw_sp_acl_rulei_act_fwd(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_rule_info *rulei,
struct net_device *out_dev)
{
struct mlxsw_sp_port *mlxsw_sp_port;
u8 local_port;
bool in_port;
if (out_dev) {
if (!mlxsw_sp_port_dev_check(out_dev))
return -EINVAL;
mlxsw_sp_port = netdev_priv(out_dev);
if (mlxsw_sp_port->mlxsw_sp != mlxsw_sp)
return -EINVAL;
local_port = mlxsw_sp_port->local_port;
in_port = false;
} else {
/* If out_dev is NULL, the called wants to
* set forward to ingress port.
*/
local_port = 0;
in_port = true;
}
return mlxsw_afa_block_append_fwd(rulei->act_block,
local_port, in_port);
}
struct mlxsw_sp_acl_rule *
mlxsw_sp_acl_rule_create(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_ruleset *ruleset,
unsigned long cookie)
{
const struct mlxsw_sp_acl_profile_ops *ops = ruleset->ht_key.ops;
struct mlxsw_sp_acl_rule *rule;
int err;
mlxsw_sp_acl_ruleset_ref_inc(ruleset);
rule = kzalloc(sizeof(*rule) + ops->rule_priv_size, GFP_KERNEL);
if (!rule) {
err = -ENOMEM;
goto err_alloc;
}
rule->cookie = cookie;
rule->ruleset = ruleset;
rule->rulei = mlxsw_sp_acl_rulei_create(mlxsw_sp->acl);
if (IS_ERR(rule->rulei)) {
err = PTR_ERR(rule->rulei);
goto err_rulei_create;
}
return rule;
err_rulei_create:
kfree(rule);
err_alloc:
mlxsw_sp_acl_ruleset_ref_dec(mlxsw_sp, ruleset);
return ERR_PTR(err);
}
void mlxsw_sp_acl_rule_destroy(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_rule *rule)
{
struct mlxsw_sp_acl_ruleset *ruleset = rule->ruleset;
mlxsw_sp_acl_rulei_destroy(rule->rulei);
kfree(rule);
mlxsw_sp_acl_ruleset_ref_dec(mlxsw_sp, ruleset);
}
int mlxsw_sp_acl_rule_add(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_rule *rule)
{
struct mlxsw_sp_acl_ruleset *ruleset = rule->ruleset;
const struct mlxsw_sp_acl_profile_ops *ops = ruleset->ht_key.ops;
int err;
err = ops->rule_add(mlxsw_sp, ruleset->priv, rule->priv, rule->rulei);
if (err)
return err;
err = rhashtable_insert_fast(&ruleset->rule_ht, &rule->ht_node,
mlxsw_sp_acl_rule_ht_params);
if (err)
goto err_rhashtable_insert;
return 0;
err_rhashtable_insert:
ops->rule_del(mlxsw_sp, rule->priv);
return err;
}
void mlxsw_sp_acl_rule_del(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_rule *rule)
{
struct mlxsw_sp_acl_ruleset *ruleset = rule->ruleset;
const struct mlxsw_sp_acl_profile_ops *ops = ruleset->ht_key.ops;
rhashtable_remove_fast(&ruleset->rule_ht, &rule->ht_node,
mlxsw_sp_acl_rule_ht_params);
ops->rule_del(mlxsw_sp, rule->priv);
}
struct mlxsw_sp_acl_rule *
mlxsw_sp_acl_rule_lookup(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_ruleset *ruleset,
unsigned long cookie)
{
return rhashtable_lookup_fast(&ruleset->rule_ht, &cookie,
mlxsw_sp_acl_rule_ht_params);
}
struct mlxsw_sp_acl_rule_info *
mlxsw_sp_acl_rule_rulei(struct mlxsw_sp_acl_rule *rule)
{
return rule->rulei;
}
#define MLXSW_SP_KDVL_ACT_EXT_SIZE 1
static int mlxsw_sp_act_kvdl_set_add(void *priv, u32 *p_kvdl_index,
char *enc_actions, bool is_first)
{
struct mlxsw_sp *mlxsw_sp = priv;
char pefa_pl[MLXSW_REG_PEFA_LEN];
u32 kvdl_index;
int ret;
int err;
/* The first action set of a TCAM entry is stored directly in TCAM,
* not KVD linear area.
*/
if (is_first)
return 0;
ret = mlxsw_sp_kvdl_alloc(mlxsw_sp, MLXSW_SP_KDVL_ACT_EXT_SIZE);
if (ret < 0)
return ret;
kvdl_index = ret;
mlxsw_reg_pefa_pack(pefa_pl, kvdl_index, enc_actions);
err = mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(pefa), pefa_pl);
if (err)
goto err_pefa_write;
*p_kvdl_index = kvdl_index;
return 0;
err_pefa_write:
mlxsw_sp_kvdl_free(mlxsw_sp, kvdl_index);
return err;
}
static void mlxsw_sp_act_kvdl_set_del(void *priv, u32 kvdl_index,
bool is_first)
{
struct mlxsw_sp *mlxsw_sp = priv;
if (is_first)
return;
mlxsw_sp_kvdl_free(mlxsw_sp, kvdl_index);
}
static int mlxsw_sp_act_kvdl_fwd_entry_add(void *priv, u32 *p_kvdl_index,
u8 local_port)
{
struct mlxsw_sp *mlxsw_sp = priv;
char ppbs_pl[MLXSW_REG_PPBS_LEN];
u32 kvdl_index;
int ret;
int err;
ret = mlxsw_sp_kvdl_alloc(mlxsw_sp, 1);
if (ret < 0)
return ret;
kvdl_index = ret;
mlxsw_reg_ppbs_pack(ppbs_pl, kvdl_index, local_port);
err = mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(ppbs), ppbs_pl);
if (err)
goto err_ppbs_write;
*p_kvdl_index = kvdl_index;
return 0;
err_ppbs_write:
mlxsw_sp_kvdl_free(mlxsw_sp, kvdl_index);
return err;
}
static void mlxsw_sp_act_kvdl_fwd_entry_del(void *priv, u32 kvdl_index)
{
struct mlxsw_sp *mlxsw_sp = priv;
mlxsw_sp_kvdl_free(mlxsw_sp, kvdl_index);
}
static const struct mlxsw_afa_ops mlxsw_sp_act_afa_ops = {
.kvdl_set_add = mlxsw_sp_act_kvdl_set_add,
.kvdl_set_del = mlxsw_sp_act_kvdl_set_del,
.kvdl_fwd_entry_add = mlxsw_sp_act_kvdl_fwd_entry_add,
.kvdl_fwd_entry_del = mlxsw_sp_act_kvdl_fwd_entry_del,
};
int mlxsw_sp_acl_init(struct mlxsw_sp *mlxsw_sp)
{
const struct mlxsw_sp_acl_ops *acl_ops = &mlxsw_sp_acl_tcam_ops;
struct mlxsw_sp_acl *acl;
int err;
acl = kzalloc(sizeof(*acl) + acl_ops->priv_size, GFP_KERNEL);
if (!acl)
return -ENOMEM;
mlxsw_sp->acl = acl;
acl->afk = mlxsw_afk_create(MLXSW_CORE_RES_GET(mlxsw_sp->core,
ACL_FLEX_KEYS),
mlxsw_sp_afk_blocks,
MLXSW_SP_AFK_BLOCKS_COUNT);
if (!acl->afk) {
err = -ENOMEM;
goto err_afk_create;
}
acl->afa = mlxsw_afa_create(MLXSW_CORE_RES_GET(mlxsw_sp->core,
ACL_ACTIONS_PER_SET),
&mlxsw_sp_act_afa_ops, mlxsw_sp);
if (IS_ERR(acl->afa)) {
err = PTR_ERR(acl->afa);
goto err_afa_create;
}
err = rhashtable_init(&acl->ruleset_ht,
&mlxsw_sp_acl_ruleset_ht_params);
if (err)
goto err_rhashtable_init;
err = acl_ops->init(mlxsw_sp, acl->priv);
if (err)
goto err_acl_ops_init;
acl->ops = acl_ops;
return 0;
err_acl_ops_init:
rhashtable_destroy(&acl->ruleset_ht);
err_rhashtable_init:
mlxsw_afa_destroy(acl->afa);
err_afa_create:
mlxsw_afk_destroy(acl->afk);
err_afk_create:
kfree(acl);
return err;
}
void mlxsw_sp_acl_fini(struct mlxsw_sp *mlxsw_sp)
{
struct mlxsw_sp_acl *acl = mlxsw_sp->acl;
const struct mlxsw_sp_acl_ops *acl_ops = acl->ops;
acl_ops->fini(mlxsw_sp, acl->priv);
rhashtable_destroy(&acl->ruleset_ht);
mlxsw_afa_destroy(acl->afa);
mlxsw_afk_destroy(acl->afk);
kfree(acl);
}
/*
* drivers/net/ethernet/mellanox/mlxsw/spectrum_acl_flex_keys.h
* Copyright (c) 2017 Mellanox Technologies. All rights reserved.
* Copyright (c) 2017 Jiri Pirko <jiri@mellanox.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
* 3. Neither the names of the copyright holders nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* Alternatively, this software may be distributed under the terms of the
* GNU General Public License ("GPL") version 2 as published by the Free
* Software Foundation.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef _MLXSW_SPECTRUM_ACL_FLEX_KEYS_H
#define _MLXSW_SPECTRUM_ACL_FLEX_KEYS_H
#include "core_acl_flex_keys.h"
static struct mlxsw_afk_element_inst mlxsw_sp_afk_element_info_l2_dmac[] = {
MLXSW_AFK_ELEMENT_INST_BUF(DMAC, 0x00, 6),
MLXSW_AFK_ELEMENT_INST_U32(SRC_SYS_PORT, 0x0C, 0, 16),
};
static struct mlxsw_afk_element_inst mlxsw_sp_afk_element_info_l2_smac[] = {
MLXSW_AFK_ELEMENT_INST_BUF(SMAC, 0x00, 6),
MLXSW_AFK_ELEMENT_INST_U32(SRC_SYS_PORT, 0x0C, 0, 16),
};
static struct mlxsw_afk_element_inst mlxsw_sp_afk_element_info_l2_smac_ex[] = {
MLXSW_AFK_ELEMENT_INST_BUF(SMAC, 0x02, 6),
MLXSW_AFK_ELEMENT_INST_U32(ETHERTYPE, 0x0C, 0, 16),
};
static struct mlxsw_afk_element_inst mlxsw_sp_afk_element_info_ipv4_sip[] = {
MLXSW_AFK_ELEMENT_INST_U32(SRC_IP4, 0x00, 0, 32),
MLXSW_AFK_ELEMENT_INST_U32(IP_PROTO, 0x08, 0, 8),
MLXSW_AFK_ELEMENT_INST_U32(SRC_SYS_PORT, 0x0C, 0, 16),
};
static struct mlxsw_afk_element_inst mlxsw_sp_afk_element_info_ipv4_dip[] = {
MLXSW_AFK_ELEMENT_INST_U32(DST_IP4, 0x00, 0, 32),
MLXSW_AFK_ELEMENT_INST_U32(IP_PROTO, 0x08, 0, 8),
MLXSW_AFK_ELEMENT_INST_U32(SRC_SYS_PORT, 0x0C, 0, 16),
};
static struct mlxsw_afk_element_inst mlxsw_sp_afk_element_info_ipv4_ex[] = {
MLXSW_AFK_ELEMENT_INST_U32(SRC_L4_PORT, 0x08, 0, 16),
MLXSW_AFK_ELEMENT_INST_U32(DST_L4_PORT, 0x0C, 0, 16),
};
static struct mlxsw_afk_element_inst mlxsw_sp_afk_element_info_ipv6_dip[] = {
MLXSW_AFK_ELEMENT_INST_BUF(DST_IP6_LO, 0x00, 8),
};
static struct mlxsw_afk_element_inst mlxsw_sp_afk_element_info_ipv6_ex1[] = {
MLXSW_AFK_ELEMENT_INST_BUF(DST_IP6_HI, 0x00, 8),
MLXSW_AFK_ELEMENT_INST_U32(IP_PROTO, 0x08, 0, 8),
};
static struct mlxsw_afk_element_inst mlxsw_sp_afk_element_info_ipv6_sip[] = {
MLXSW_AFK_ELEMENT_INST_BUF(SRC_IP6_LO, 0x00, 8),
};
static struct mlxsw_afk_element_inst mlxsw_sp_afk_element_info_ipv6_sip_ex[] = {
MLXSW_AFK_ELEMENT_INST_BUF(SRC_IP6_HI, 0x00, 8),
};
static struct mlxsw_afk_element_inst mlxsw_sp_afk_element_info_packet_type[] = {
MLXSW_AFK_ELEMENT_INST_U32(ETHERTYPE, 0x00, 0, 16),
};
static const struct mlxsw_afk_block mlxsw_sp_afk_blocks[] = {
MLXSW_AFK_BLOCK(0x10, mlxsw_sp_afk_element_info_l2_dmac),
MLXSW_AFK_BLOCK(0x11, mlxsw_sp_afk_element_info_l2_smac),
MLXSW_AFK_BLOCK(0x12, mlxsw_sp_afk_element_info_l2_smac_ex),
MLXSW_AFK_BLOCK(0x30, mlxsw_sp_afk_element_info_ipv4_sip),
MLXSW_AFK_BLOCK(0x31, mlxsw_sp_afk_element_info_ipv4_dip),
MLXSW_AFK_BLOCK(0x33, mlxsw_sp_afk_element_info_ipv4_ex),
MLXSW_AFK_BLOCK(0x60, mlxsw_sp_afk_element_info_ipv6_dip),
MLXSW_AFK_BLOCK(0x65, mlxsw_sp_afk_element_info_ipv6_ex1),
MLXSW_AFK_BLOCK(0x62, mlxsw_sp_afk_element_info_ipv6_sip),
MLXSW_AFK_BLOCK(0x63, mlxsw_sp_afk_element_info_ipv6_sip_ex),
MLXSW_AFK_BLOCK(0xB0, mlxsw_sp_afk_element_info_packet_type),
};
#define MLXSW_SP_AFK_BLOCKS_COUNT ARRAY_SIZE(mlxsw_sp_afk_blocks)
#endif
/*
* drivers/net/ethernet/mellanox/mlxsw/spectrum_acl_tcam.c
* Copyright (c) 2017 Mellanox Technologies. All rights reserved.
* Copyright (c) 2017 Jiri Pirko <jiri@mellanox.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
* 3. Neither the names of the copyright holders nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* Alternatively, this software may be distributed under the terms of the
* GNU General Public License ("GPL") version 2 as published by the Free
* Software Foundation.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/bitops.h>
#include <linux/list.h>
#include <linux/rhashtable.h>
#include <linux/netdevice.h>
#include <linux/parman.h>
#include "reg.h"
#include "core.h"
#include "resources.h"
#include "spectrum.h"
#include "core_acl_flex_keys.h"
struct mlxsw_sp_acl_tcam {
unsigned long *used_regions; /* bit array */
unsigned int max_regions;
unsigned long *used_groups; /* bit array */
unsigned int max_groups;
unsigned int max_group_size;
};
static int mlxsw_sp_acl_tcam_init(struct mlxsw_sp *mlxsw_sp, void *priv)
{
struct mlxsw_sp_acl_tcam *tcam = priv;
u64 max_tcam_regions;
u64 max_regions;
u64 max_groups;
size_t alloc_size;
int err;
max_tcam_regions = MLXSW_CORE_RES_GET(mlxsw_sp->core,
ACL_MAX_TCAM_REGIONS);
max_regions = MLXSW_CORE_RES_GET(mlxsw_sp->core, ACL_MAX_REGIONS);
/* Use 1:1 mapping between ACL region and TCAM region */
if (max_tcam_regions < max_regions)
max_regions = max_tcam_regions;
alloc_size = sizeof(tcam->used_regions[0]) * BITS_TO_LONGS(max_regions);
tcam->used_regions = kzalloc(alloc_size, GFP_KERNEL);
if (!tcam->used_regions)
return -ENOMEM;
tcam->max_regions = max_regions;
max_groups = MLXSW_CORE_RES_GET(mlxsw_sp->core, ACL_MAX_GROUPS);
alloc_size = sizeof(tcam->used_groups[0]) * BITS_TO_LONGS(max_groups);
tcam->used_groups = kzalloc(alloc_size, GFP_KERNEL);
if (!tcam->used_groups) {
err = -ENOMEM;
goto err_alloc_used_groups;
}
tcam->max_groups = max_groups;
tcam->max_group_size = MLXSW_CORE_RES_GET(mlxsw_sp->core,
ACL_MAX_GROUP_SIZE);
return 0;
err_alloc_used_groups:
kfree(tcam->used_regions);
return err;
}
static void mlxsw_sp_acl_tcam_fini(struct mlxsw_sp *mlxsw_sp, void *priv)
{
struct mlxsw_sp_acl_tcam *tcam = priv;
kfree(tcam->used_groups);
kfree(tcam->used_regions);
}
static int mlxsw_sp_acl_tcam_region_id_get(struct mlxsw_sp_acl_tcam *tcam,
u16 *p_id)
{
u16 id;
id = find_first_zero_bit(tcam->used_regions, tcam->max_regions);
if (id < tcam->max_regions) {
__set_bit(id, tcam->used_regions);
*p_id = id;
return 0;
}
return -ENOBUFS;
}
static void mlxsw_sp_acl_tcam_region_id_put(struct mlxsw_sp_acl_tcam *tcam,
u16 id)
{
__clear_bit(id, tcam->used_regions);
}
static int mlxsw_sp_acl_tcam_group_id_get(struct mlxsw_sp_acl_tcam *tcam,
u16 *p_id)
{
u16 id;
id = find_first_zero_bit(tcam->used_groups, tcam->max_groups);
if (id < tcam->max_groups) {
__set_bit(id, tcam->used_groups);
*p_id = id;
return 0;
}
return -ENOBUFS;
}
static void mlxsw_sp_acl_tcam_group_id_put(struct mlxsw_sp_acl_tcam *tcam,
u16 id)
{
__clear_bit(id, tcam->used_groups);
}
struct mlxsw_sp_acl_tcam_pattern {
const enum mlxsw_afk_element *elements;
unsigned int elements_count;
};
struct mlxsw_sp_acl_tcam_group {
struct mlxsw_sp_acl_tcam *tcam;
u16 id;
struct list_head region_list;
unsigned int region_count;
struct rhashtable chunk_ht;
struct {
u16 local_port;
bool ingress;
} bound;
struct mlxsw_sp_acl_tcam_group_ops *ops;
const struct mlxsw_sp_acl_tcam_pattern *patterns;
unsigned int patterns_count;
};
struct mlxsw_sp_acl_tcam_region {
struct list_head list; /* Member of a TCAM group */
struct list_head chunk_list; /* List of chunks under this region */
struct parman *parman;
struct mlxsw_sp *mlxsw_sp;
struct mlxsw_sp_acl_tcam_group *group;
u16 id; /* ACL ID and region ID - they are same */
char tcam_region_info[MLXSW_REG_PXXX_TCAM_REGION_INFO_LEN];
struct mlxsw_afk_key_info *key_info;
struct {
struct parman_prio parman_prio;
struct parman_item parman_item;
struct mlxsw_sp_acl_rule_info *rulei;
} catchall;
};
struct mlxsw_sp_acl_tcam_chunk {
struct list_head list; /* Member of a TCAM region */
struct rhash_head ht_node; /* Member of a chunk HT */
unsigned int priority; /* Priority within the region and group */
struct parman_prio parman_prio;
struct mlxsw_sp_acl_tcam_group *group;
struct mlxsw_sp_acl_tcam_region *region;
unsigned int ref_count;
};
struct mlxsw_sp_acl_tcam_entry {
struct parman_item parman_item;
struct mlxsw_sp_acl_tcam_chunk *chunk;
};
static const struct rhashtable_params mlxsw_sp_acl_tcam_chunk_ht_params = {
.key_len = sizeof(unsigned int),
.key_offset = offsetof(struct mlxsw_sp_acl_tcam_chunk, priority),
.head_offset = offsetof(struct mlxsw_sp_acl_tcam_chunk, ht_node),
.automatic_shrinking = true,
};
static int mlxsw_sp_acl_tcam_group_update(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_tcam_group *group)
{
struct mlxsw_sp_acl_tcam_region *region;
char pagt_pl[MLXSW_REG_PAGT_LEN];
int acl_index = 0;
mlxsw_reg_pagt_pack(pagt_pl, group->id);
list_for_each_entry(region, &group->region_list, list)
mlxsw_reg_pagt_acl_id_pack(pagt_pl, acl_index++, region->id);
mlxsw_reg_pagt_size_set(pagt_pl, acl_index);
return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(pagt), pagt_pl);
}
static int
mlxsw_sp_acl_tcam_group_add(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_tcam *tcam,
struct mlxsw_sp_acl_tcam_group *group,
const struct mlxsw_sp_acl_tcam_pattern *patterns,
unsigned int patterns_count)
{
int err;
group->tcam = tcam;
group->patterns = patterns;
group->patterns_count = patterns_count;
INIT_LIST_HEAD(&group->region_list);
err = mlxsw_sp_acl_tcam_group_id_get(tcam, &group->id);
if (err)
return err;
err = mlxsw_sp_acl_tcam_group_update(mlxsw_sp, group);
if (err)
goto err_group_update;
err = rhashtable_init(&group->chunk_ht,
&mlxsw_sp_acl_tcam_chunk_ht_params);
if (err)
goto err_rhashtable_init;
return 0;
err_rhashtable_init:
err_group_update:
mlxsw_sp_acl_tcam_group_id_put(tcam, group->id);
return err;
}
static void mlxsw_sp_acl_tcam_group_del(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_tcam_group *group)
{
struct mlxsw_sp_acl_tcam *tcam = group->tcam;
rhashtable_destroy(&group->chunk_ht);
mlxsw_sp_acl_tcam_group_id_put(tcam, group->id);
WARN_ON(!list_empty(&group->region_list));
}
static int
mlxsw_sp_acl_tcam_group_bind(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_tcam_group *group,
struct net_device *dev, bool ingress)
{
struct mlxsw_sp_port *mlxsw_sp_port;
char ppbt_pl[MLXSW_REG_PPBT_LEN];
if (!mlxsw_sp_port_dev_check(dev))
return -EINVAL;
mlxsw_sp_port = netdev_priv(dev);
group->bound.local_port = mlxsw_sp_port->local_port;
group->bound.ingress = ingress;
mlxsw_reg_ppbt_pack(ppbt_pl,
group->bound.ingress ? MLXSW_REG_PXBT_E_IACL :
MLXSW_REG_PXBT_E_EACL,
MLXSW_REG_PXBT_OP_BIND, group->bound.local_port,
group->id);
return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(ppbt), ppbt_pl);
}
static void
mlxsw_sp_acl_tcam_group_unbind(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_tcam_group *group)
{
char ppbt_pl[MLXSW_REG_PPBT_LEN];
mlxsw_reg_ppbt_pack(ppbt_pl,
group->bound.ingress ? MLXSW_REG_PXBT_E_IACL :
MLXSW_REG_PXBT_E_EACL,
MLXSW_REG_PXBT_OP_UNBIND, group->bound.local_port,
group->id);
mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(ppbt), ppbt_pl);
}
static unsigned int
mlxsw_sp_acl_tcam_region_prio(struct mlxsw_sp_acl_tcam_region *region)
{
struct mlxsw_sp_acl_tcam_chunk *chunk;
if (list_empty(&region->chunk_list))
return 0;
/* As a priority of a region, return priority of the first chunk */
chunk = list_first_entry(&region->chunk_list, typeof(*chunk), list);
return chunk->priority;
}
static unsigned int
mlxsw_sp_acl_tcam_region_max_prio(struct mlxsw_sp_acl_tcam_region *region)
{
struct mlxsw_sp_acl_tcam_chunk *chunk;
if (list_empty(&region->chunk_list))
return 0;
chunk = list_last_entry(&region->chunk_list, typeof(*chunk), list);
return chunk->priority;
}
static void
mlxsw_sp_acl_tcam_group_list_add(struct mlxsw_sp_acl_tcam_group *group,
struct mlxsw_sp_acl_tcam_region *region)
{
struct mlxsw_sp_acl_tcam_region *region2;
struct list_head *pos;
/* Position the region inside the list according to priority */
list_for_each(pos, &group->region_list) {
region2 = list_entry(pos, typeof(*region2), list);
if (mlxsw_sp_acl_tcam_region_prio(region2) >
mlxsw_sp_acl_tcam_region_prio(region))
break;
}
list_add_tail(&region->list, pos);
group->region_count++;
}
static void
mlxsw_sp_acl_tcam_group_list_del(struct mlxsw_sp_acl_tcam_group *group,
struct mlxsw_sp_acl_tcam_region *region)
{
group->region_count--;
list_del(&region->list);
}
static int
mlxsw_sp_acl_tcam_group_region_attach(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_tcam_group *group,
struct mlxsw_sp_acl_tcam_region *region)
{
int err;
if (group->region_count == group->tcam->max_group_size)
return -ENOBUFS;
mlxsw_sp_acl_tcam_group_list_add(group, region);
err = mlxsw_sp_acl_tcam_group_update(mlxsw_sp, group);
if (err)
goto err_group_update;
region->group = group;
return 0;
err_group_update:
mlxsw_sp_acl_tcam_group_list_del(group, region);
mlxsw_sp_acl_tcam_group_update(mlxsw_sp, group);
return err;
}
static void
mlxsw_sp_acl_tcam_group_region_detach(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_tcam_region *region)
{
struct mlxsw_sp_acl_tcam_group *group = region->group;
mlxsw_sp_acl_tcam_group_list_del(group, region);
mlxsw_sp_acl_tcam_group_update(mlxsw_sp, group);
}
static struct mlxsw_sp_acl_tcam_region *
mlxsw_sp_acl_tcam_group_region_find(struct mlxsw_sp_acl_tcam_group *group,
unsigned int priority,
struct mlxsw_afk_element_usage *elusage,
bool *p_need_split)
{
struct mlxsw_sp_acl_tcam_region *region, *region2;
struct list_head *pos;
bool issubset;
list_for_each(pos, &group->region_list) {
region = list_entry(pos, typeof(*region), list);
/* First, check if the requested priority does not rather belong
* under some of the next regions.
*/
if (pos->next != &group->region_list) { /* not last */
region2 = list_entry(pos->next, typeof(*region2), list);
if (priority >= mlxsw_sp_acl_tcam_region_prio(region2))
continue;
}
issubset = mlxsw_afk_key_info_subset(region->key_info, elusage);
/* If requested element usage would not fit and the priority
* is lower than the currently inspected region we cannot
* use this region, so return NULL to indicate new region has
* to be created.
*/
if (!issubset &&
priority < mlxsw_sp_acl_tcam_region_prio(region))
return NULL;
/* If requested element usage would not fit and the priority
* is higher than the currently inspected region we cannot
* use this region. There is still some hope that the next
* region would be the fit. So let it be processed and
* eventually break at the check right above this.
*/
if (!issubset &&
priority > mlxsw_sp_acl_tcam_region_max_prio(region))
continue;
/* Indicate if the region needs to be split in order to add
* the requested priority. Split is needed when requested
* element usage won't fit into the found region.
*/
*p_need_split = !issubset;
return region;
}
return NULL; /* New region has to be created. */
}
static void
mlxsw_sp_acl_tcam_group_use_patterns(struct mlxsw_sp_acl_tcam_group *group,
struct mlxsw_afk_element_usage *elusage,
struct mlxsw_afk_element_usage *out)
{
const struct mlxsw_sp_acl_tcam_pattern *pattern;
int i;
for (i = 0; i < group->patterns_count; i++) {
pattern = &group->patterns[i];
mlxsw_afk_element_usage_fill(out, pattern->elements,
pattern->elements_count);
if (mlxsw_afk_element_usage_subset(elusage, out))
return;
}
memcpy(out, elusage, sizeof(*out));
}
#define MLXSW_SP_ACL_TCAM_REGION_BASE_COUNT 16
#define MLXSW_SP_ACL_TCAM_REGION_RESIZE_STEP 16
static int
mlxsw_sp_acl_tcam_region_alloc(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_tcam_region *region)
{
struct mlxsw_afk_key_info *key_info = region->key_info;
char ptar_pl[MLXSW_REG_PTAR_LEN];
unsigned int encodings_count;
int i;
int err;
mlxsw_reg_ptar_pack(ptar_pl, MLXSW_REG_PTAR_OP_ALLOC,
MLXSW_SP_ACL_TCAM_REGION_BASE_COUNT,
region->id, region->tcam_region_info);
encodings_count = mlxsw_afk_key_info_blocks_count_get(key_info);
for (i = 0; i < encodings_count; i++) {
u16 encoding;
encoding = mlxsw_afk_key_info_block_encoding_get(key_info, i);
mlxsw_reg_ptar_key_id_pack(ptar_pl, i, encoding);
}
err = mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(ptar), ptar_pl);
if (err)
return err;
mlxsw_reg_ptar_unpack(ptar_pl, region->tcam_region_info);
return 0;
}
static void
mlxsw_sp_acl_tcam_region_free(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_tcam_region *region)
{
char ptar_pl[MLXSW_REG_PTAR_LEN];
mlxsw_reg_ptar_pack(ptar_pl, MLXSW_REG_PTAR_OP_FREE, 0, region->id,
region->tcam_region_info);
mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(ptar), ptar_pl);
}
static int
mlxsw_sp_acl_tcam_region_resize(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_tcam_region *region,
u16 new_size)
{
char ptar_pl[MLXSW_REG_PTAR_LEN];
mlxsw_reg_ptar_pack(ptar_pl, MLXSW_REG_PTAR_OP_RESIZE,
new_size, region->id, region->tcam_region_info);
return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(ptar), ptar_pl);
}
static int
mlxsw_sp_acl_tcam_region_enable(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_tcam_region *region)
{
char pacl_pl[MLXSW_REG_PACL_LEN];
mlxsw_reg_pacl_pack(pacl_pl, region->id, true,
region->tcam_region_info);
return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(pacl), pacl_pl);
}
static void
mlxsw_sp_acl_tcam_region_disable(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_tcam_region *region)
{
char pacl_pl[MLXSW_REG_PACL_LEN];
mlxsw_reg_pacl_pack(pacl_pl, region->id, false,
region->tcam_region_info);
mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(pacl), pacl_pl);
}
static int
mlxsw_sp_acl_tcam_region_entry_insert(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_tcam_region *region,
unsigned int offset,
struct mlxsw_sp_acl_rule_info *rulei)
{
char ptce2_pl[MLXSW_REG_PTCE2_LEN];
char *act_set;
char *mask;
char *key;
mlxsw_reg_ptce2_pack(ptce2_pl, true, MLXSW_REG_PTCE2_OP_WRITE_WRITE,
region->tcam_region_info, offset);
key = mlxsw_reg_ptce2_flex_key_blocks_data(ptce2_pl);
mask = mlxsw_reg_ptce2_mask_data(ptce2_pl);
mlxsw_afk_encode(region->key_info, &rulei->values, key, mask);
/* Only the first action set belongs here, the rest is in KVD */
act_set = mlxsw_afa_block_first_set(rulei->act_block);
mlxsw_reg_ptce2_flex_action_set_memcpy_to(ptce2_pl, act_set);
return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(ptce2), ptce2_pl);
}
static void
mlxsw_sp_acl_tcam_region_entry_remove(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_tcam_region *region,
unsigned int offset)
{
char ptce2_pl[MLXSW_REG_PTCE2_LEN];
mlxsw_reg_ptce2_pack(ptce2_pl, false, MLXSW_REG_PTCE2_OP_WRITE_WRITE,
region->tcam_region_info, offset);
mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(ptce2), ptce2_pl);
}
#define MLXSW_SP_ACL_TCAM_CATCHALL_PRIO (-1UL)
static int
mlxsw_sp_acl_tcam_region_catchall_add(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_tcam_region *region)
{
struct parman_prio *parman_prio = &region->catchall.parman_prio;
struct parman_item *parman_item = &region->catchall.parman_item;
struct mlxsw_sp_acl_rule_info *rulei;
int err;
parman_prio_init(region->parman, parman_prio,
MLXSW_SP_ACL_TCAM_CATCHALL_PRIO);
err = parman_item_add(region->parman, parman_prio, parman_item);
if (err)
goto err_parman_item_add;
rulei = mlxsw_sp_acl_rulei_create(mlxsw_sp->acl);
if (IS_ERR(rulei)) {
err = PTR_ERR(rulei);
goto err_rulei_create;
}
mlxsw_sp_acl_rulei_act_continue(rulei);
err = mlxsw_sp_acl_rulei_commit(rulei);
if (err)
goto err_rulei_commit;
err = mlxsw_sp_acl_tcam_region_entry_insert(mlxsw_sp, region,
parman_item->index, rulei);
region->catchall.rulei = rulei;
if (err)
goto err_rule_insert;
return 0;
err_rule_insert:
err_rulei_commit:
mlxsw_sp_acl_rulei_destroy(rulei);
err_rulei_create:
parman_item_remove(region->parman, parman_prio, parman_item);
err_parman_item_add:
parman_prio_fini(parman_prio);
return err;
}
static void
mlxsw_sp_acl_tcam_region_catchall_del(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_tcam_region *region)
{
struct parman_prio *parman_prio = &region->catchall.parman_prio;
struct parman_item *parman_item = &region->catchall.parman_item;
struct mlxsw_sp_acl_rule_info *rulei = region->catchall.rulei;
mlxsw_sp_acl_tcam_region_entry_remove(mlxsw_sp, region,
parman_item->index);
mlxsw_sp_acl_rulei_destroy(rulei);
parman_item_remove(region->parman, parman_prio, parman_item);
parman_prio_fini(parman_prio);
}
static void
mlxsw_sp_acl_tcam_region_move(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_tcam_region *region,
u16 src_offset, u16 dst_offset, u16 size)
{
char prcr_pl[MLXSW_REG_PRCR_LEN];
mlxsw_reg_prcr_pack(prcr_pl, MLXSW_REG_PRCR_OP_MOVE,
region->tcam_region_info, src_offset,
region->tcam_region_info, dst_offset, size);
mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(prcr), prcr_pl);
}
static int mlxsw_sp_acl_tcam_region_parman_resize(void *priv,
unsigned long new_count)
{
struct mlxsw_sp_acl_tcam_region *region = priv;
struct mlxsw_sp *mlxsw_sp = region->mlxsw_sp;
u64 max_tcam_rules;
max_tcam_rules = MLXSW_CORE_RES_GET(mlxsw_sp->core, ACL_MAX_TCAM_RULES);
if (new_count > max_tcam_rules)
return -EINVAL;
return mlxsw_sp_acl_tcam_region_resize(mlxsw_sp, region, new_count);
}
static void mlxsw_sp_acl_tcam_region_parman_move(void *priv,
unsigned long from_index,
unsigned long to_index,
unsigned long count)
{
struct mlxsw_sp_acl_tcam_region *region = priv;
struct mlxsw_sp *mlxsw_sp = region->mlxsw_sp;
mlxsw_sp_acl_tcam_region_move(mlxsw_sp, region,
from_index, to_index, count);
}
static const struct parman_ops mlxsw_sp_acl_tcam_region_parman_ops = {
.base_count = MLXSW_SP_ACL_TCAM_REGION_BASE_COUNT,
.resize_step = MLXSW_SP_ACL_TCAM_REGION_RESIZE_STEP,
.resize = mlxsw_sp_acl_tcam_region_parman_resize,
.move = mlxsw_sp_acl_tcam_region_parman_move,
.algo = PARMAN_ALGO_TYPE_LSORT,
};
static struct mlxsw_sp_acl_tcam_region *
mlxsw_sp_acl_tcam_region_create(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_tcam *tcam,
struct mlxsw_afk_element_usage *elusage)
{
struct mlxsw_afk *afk = mlxsw_sp_acl_afk(mlxsw_sp->acl);
struct mlxsw_sp_acl_tcam_region *region;
int err;
region = kzalloc(sizeof(*region), GFP_KERNEL);
if (!region)
return ERR_PTR(-ENOMEM);
INIT_LIST_HEAD(&region->chunk_list);
region->mlxsw_sp = mlxsw_sp;
region->parman = parman_create(&mlxsw_sp_acl_tcam_region_parman_ops,
region);
if (!region->parman) {
err = -ENOMEM;
goto err_parman_create;
}
region->key_info = mlxsw_afk_key_info_get(afk, elusage);
if (IS_ERR(region->key_info)) {
err = PTR_ERR(region->key_info);
goto err_key_info_get;
}
err = mlxsw_sp_acl_tcam_region_id_get(tcam, &region->id);
if (err)
goto err_region_id_get;
err = mlxsw_sp_acl_tcam_region_alloc(mlxsw_sp, region);
if (err)
goto err_tcam_region_alloc;
err = mlxsw_sp_acl_tcam_region_enable(mlxsw_sp, region);
if (err)
goto err_tcam_region_enable;
err = mlxsw_sp_acl_tcam_region_catchall_add(mlxsw_sp, region);
if (err)
goto err_tcam_region_catchall_add;
return region;
err_tcam_region_catchall_add:
mlxsw_sp_acl_tcam_region_disable(mlxsw_sp, region);
err_tcam_region_enable:
mlxsw_sp_acl_tcam_region_free(mlxsw_sp, region);
err_tcam_region_alloc:
mlxsw_sp_acl_tcam_region_id_put(tcam, region->id);
err_region_id_get:
mlxsw_afk_key_info_put(region->key_info);
err_key_info_get:
parman_destroy(region->parman);
err_parman_create:
kfree(region);
return ERR_PTR(err);
}
static void
mlxsw_sp_acl_tcam_region_destroy(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_tcam_region *region)
{
mlxsw_sp_acl_tcam_region_catchall_del(mlxsw_sp, region);
mlxsw_sp_acl_tcam_region_disable(mlxsw_sp, region);
mlxsw_sp_acl_tcam_region_free(mlxsw_sp, region);
mlxsw_sp_acl_tcam_region_id_put(region->group->tcam, region->id);
mlxsw_afk_key_info_put(region->key_info);
parman_destroy(region->parman);
kfree(region);
}
static int
mlxsw_sp_acl_tcam_chunk_assoc(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_tcam_group *group,
unsigned int priority,
struct mlxsw_afk_element_usage *elusage,
struct mlxsw_sp_acl_tcam_chunk *chunk)
{
struct mlxsw_sp_acl_tcam_region *region;
bool region_created = false;
bool need_split;
int err;
region = mlxsw_sp_acl_tcam_group_region_find(group, priority, elusage,
&need_split);
if (region && need_split) {
/* According to priority, the chunk should belong to an
* existing region. However, this chunk needs elements
* that region does not contain. We need to split the existing
* region into two and create a new region for this chunk
* in between. This is not supported now.
*/
return -EOPNOTSUPP;
}
if (!region) {
struct mlxsw_afk_element_usage region_elusage;
mlxsw_sp_acl_tcam_group_use_patterns(group, elusage,
&region_elusage);
region = mlxsw_sp_acl_tcam_region_create(mlxsw_sp, group->tcam,
&region_elusage);
if (IS_ERR(region))
return PTR_ERR(region);
region_created = true;
}
chunk->region = region;
list_add_tail(&chunk->list, &region->chunk_list);
if (!region_created)
return 0;
err = mlxsw_sp_acl_tcam_group_region_attach(mlxsw_sp, group, region);
if (err)
goto err_group_region_attach;
return 0;
err_group_region_attach:
mlxsw_sp_acl_tcam_region_destroy(mlxsw_sp, region);
return err;
}
static void
mlxsw_sp_acl_tcam_chunk_deassoc(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_tcam_chunk *chunk)
{
struct mlxsw_sp_acl_tcam_region *region = chunk->region;
list_del(&chunk->list);
if (list_empty(&region->chunk_list)) {
mlxsw_sp_acl_tcam_group_region_detach(mlxsw_sp, region);
mlxsw_sp_acl_tcam_region_destroy(mlxsw_sp, region);
}
}
static struct mlxsw_sp_acl_tcam_chunk *
mlxsw_sp_acl_tcam_chunk_create(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_tcam_group *group,
unsigned int priority,
struct mlxsw_afk_element_usage *elusage)
{
struct mlxsw_sp_acl_tcam_chunk *chunk;
int err;
if (priority == MLXSW_SP_ACL_TCAM_CATCHALL_PRIO)
return ERR_PTR(-EINVAL);
chunk = kzalloc(sizeof(*chunk), GFP_KERNEL);
if (!chunk)
return ERR_PTR(-ENOMEM);
chunk->priority = priority;
chunk->group = group;
chunk->ref_count = 1;
err = mlxsw_sp_acl_tcam_chunk_assoc(mlxsw_sp, group, priority,
elusage, chunk);
if (err)
goto err_chunk_assoc;
parman_prio_init(chunk->region->parman, &chunk->parman_prio, priority);
err = rhashtable_insert_fast(&group->chunk_ht, &chunk->ht_node,
mlxsw_sp_acl_tcam_chunk_ht_params);
if (err)
goto err_rhashtable_insert;
return chunk;
err_rhashtable_insert:
parman_prio_fini(&chunk->parman_prio);
mlxsw_sp_acl_tcam_chunk_deassoc(mlxsw_sp, chunk);
err_chunk_assoc:
kfree(chunk);
return ERR_PTR(err);
}
static void
mlxsw_sp_acl_tcam_chunk_destroy(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_tcam_chunk *chunk)
{
struct mlxsw_sp_acl_tcam_group *group = chunk->group;
rhashtable_remove_fast(&group->chunk_ht, &chunk->ht_node,
mlxsw_sp_acl_tcam_chunk_ht_params);
parman_prio_fini(&chunk->parman_prio);
mlxsw_sp_acl_tcam_chunk_deassoc(mlxsw_sp, chunk);
kfree(chunk);
}
static struct mlxsw_sp_acl_tcam_chunk *
mlxsw_sp_acl_tcam_chunk_get(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_tcam_group *group,
unsigned int priority,
struct mlxsw_afk_element_usage *elusage)
{
struct mlxsw_sp_acl_tcam_chunk *chunk;
chunk = rhashtable_lookup_fast(&group->chunk_ht, &priority,
mlxsw_sp_acl_tcam_chunk_ht_params);
if (chunk) {
if (WARN_ON(!mlxsw_afk_key_info_subset(chunk->region->key_info,
elusage)))
return ERR_PTR(-EINVAL);
chunk->ref_count++;
return chunk;
}
return mlxsw_sp_acl_tcam_chunk_create(mlxsw_sp, group,
priority, elusage);
}
static void mlxsw_sp_acl_tcam_chunk_put(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_tcam_chunk *chunk)
{
if (--chunk->ref_count)
return;
mlxsw_sp_acl_tcam_chunk_destroy(mlxsw_sp, chunk);
}
static int mlxsw_sp_acl_tcam_entry_add(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_tcam_group *group,
struct mlxsw_sp_acl_tcam_entry *entry,
struct mlxsw_sp_acl_rule_info *rulei)
{
struct mlxsw_sp_acl_tcam_chunk *chunk;
struct mlxsw_sp_acl_tcam_region *region;
int err;
chunk = mlxsw_sp_acl_tcam_chunk_get(mlxsw_sp, group, rulei->priority,
&rulei->values.elusage);
if (IS_ERR(chunk))
return PTR_ERR(chunk);
region = chunk->region;
err = parman_item_add(region->parman, &chunk->parman_prio,
&entry->parman_item);
if (err)
goto err_parman_item_add;
err = mlxsw_sp_acl_tcam_region_entry_insert(mlxsw_sp, region,
entry->parman_item.index,
rulei);
if (err)
goto err_rule_insert;
entry->chunk = chunk;
return 0;
err_rule_insert:
parman_item_remove(region->parman, &chunk->parman_prio,
&entry->parman_item);
err_parman_item_add:
mlxsw_sp_acl_tcam_chunk_put(mlxsw_sp, chunk);
return err;
}
static void mlxsw_sp_acl_tcam_entry_del(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_tcam_entry *entry)
{
struct mlxsw_sp_acl_tcam_chunk *chunk = entry->chunk;
struct mlxsw_sp_acl_tcam_region *region = chunk->region;
mlxsw_sp_acl_tcam_region_entry_remove(mlxsw_sp, region,
entry->parman_item.index);
parman_item_remove(region->parman, &chunk->parman_prio,
&entry->parman_item);
mlxsw_sp_acl_tcam_chunk_put(mlxsw_sp, chunk);
}
static const enum mlxsw_afk_element mlxsw_sp_acl_tcam_pattern_ipv4[] = {
MLXSW_AFK_ELEMENT_SRC_SYS_PORT,
MLXSW_AFK_ELEMENT_DMAC,
MLXSW_AFK_ELEMENT_SMAC,
MLXSW_AFK_ELEMENT_ETHERTYPE,
MLXSW_AFK_ELEMENT_IP_PROTO,
MLXSW_AFK_ELEMENT_SRC_IP4,
MLXSW_AFK_ELEMENT_DST_IP4,
MLXSW_AFK_ELEMENT_DST_L4_PORT,
MLXSW_AFK_ELEMENT_SRC_L4_PORT,
};
static const enum mlxsw_afk_element mlxsw_sp_acl_tcam_pattern_ipv6[] = {
MLXSW_AFK_ELEMENT_ETHERTYPE,
MLXSW_AFK_ELEMENT_IP_PROTO,
MLXSW_AFK_ELEMENT_SRC_IP6_HI,
MLXSW_AFK_ELEMENT_SRC_IP6_LO,
MLXSW_AFK_ELEMENT_DST_IP6_HI,
MLXSW_AFK_ELEMENT_DST_IP6_LO,
MLXSW_AFK_ELEMENT_DST_L4_PORT,
MLXSW_AFK_ELEMENT_SRC_L4_PORT,
};
static const struct mlxsw_sp_acl_tcam_pattern mlxsw_sp_acl_tcam_patterns[] = {
{
.elements = mlxsw_sp_acl_tcam_pattern_ipv4,
.elements_count = ARRAY_SIZE(mlxsw_sp_acl_tcam_pattern_ipv4),
},
{
.elements = mlxsw_sp_acl_tcam_pattern_ipv6,
.elements_count = ARRAY_SIZE(mlxsw_sp_acl_tcam_pattern_ipv6),
},
};
#define MLXSW_SP_ACL_TCAM_PATTERNS_COUNT \
ARRAY_SIZE(mlxsw_sp_acl_tcam_patterns)
struct mlxsw_sp_acl_tcam_flower_ruleset {
struct mlxsw_sp_acl_tcam_group group;
};
struct mlxsw_sp_acl_tcam_flower_rule {
struct mlxsw_sp_acl_tcam_entry entry;
};
static int
mlxsw_sp_acl_tcam_flower_ruleset_add(struct mlxsw_sp *mlxsw_sp,
void *priv, void *ruleset_priv)
{
struct mlxsw_sp_acl_tcam_flower_ruleset *ruleset = ruleset_priv;
struct mlxsw_sp_acl_tcam *tcam = priv;
return mlxsw_sp_acl_tcam_group_add(mlxsw_sp, tcam, &ruleset->group,
mlxsw_sp_acl_tcam_patterns,
MLXSW_SP_ACL_TCAM_PATTERNS_COUNT);
}
static void
mlxsw_sp_acl_tcam_flower_ruleset_del(struct mlxsw_sp *mlxsw_sp,
void *ruleset_priv)
{
struct mlxsw_sp_acl_tcam_flower_ruleset *ruleset = ruleset_priv;
mlxsw_sp_acl_tcam_group_del(mlxsw_sp, &ruleset->group);
}
static int
mlxsw_sp_acl_tcam_flower_ruleset_bind(struct mlxsw_sp *mlxsw_sp,
void *ruleset_priv,
struct net_device *dev, bool ingress)
{
struct mlxsw_sp_acl_tcam_flower_ruleset *ruleset = ruleset_priv;
return mlxsw_sp_acl_tcam_group_bind(mlxsw_sp, &ruleset->group,
dev, ingress);
}
static void
mlxsw_sp_acl_tcam_flower_ruleset_unbind(struct mlxsw_sp *mlxsw_sp,
void *ruleset_priv)
{
struct mlxsw_sp_acl_tcam_flower_ruleset *ruleset = ruleset_priv;
mlxsw_sp_acl_tcam_group_unbind(mlxsw_sp, &ruleset->group);
}
static int
mlxsw_sp_acl_tcam_flower_rule_add(struct mlxsw_sp *mlxsw_sp,
void *ruleset_priv, void *rule_priv,
struct mlxsw_sp_acl_rule_info *rulei)
{
struct mlxsw_sp_acl_tcam_flower_ruleset *ruleset = ruleset_priv;
struct mlxsw_sp_acl_tcam_flower_rule *rule = rule_priv;
return mlxsw_sp_acl_tcam_entry_add(mlxsw_sp, &ruleset->group,
&rule->entry, rulei);
}
static void
mlxsw_sp_acl_tcam_flower_rule_del(struct mlxsw_sp *mlxsw_sp, void *rule_priv)
{
struct mlxsw_sp_acl_tcam_flower_rule *rule = rule_priv;
mlxsw_sp_acl_tcam_entry_del(mlxsw_sp, &rule->entry);
}
static const struct mlxsw_sp_acl_profile_ops mlxsw_sp_acl_tcam_flower_ops = {
.ruleset_priv_size = sizeof(struct mlxsw_sp_acl_tcam_flower_ruleset),
.ruleset_add = mlxsw_sp_acl_tcam_flower_ruleset_add,
.ruleset_del = mlxsw_sp_acl_tcam_flower_ruleset_del,
.ruleset_bind = mlxsw_sp_acl_tcam_flower_ruleset_bind,
.ruleset_unbind = mlxsw_sp_acl_tcam_flower_ruleset_unbind,
.rule_priv_size = sizeof(struct mlxsw_sp_acl_tcam_flower_rule),
.rule_add = mlxsw_sp_acl_tcam_flower_rule_add,
.rule_del = mlxsw_sp_acl_tcam_flower_rule_del,
};
static const struct mlxsw_sp_acl_profile_ops *
mlxsw_sp_acl_tcam_profile_ops_arr[] = {
[MLXSW_SP_ACL_PROFILE_FLOWER] = &mlxsw_sp_acl_tcam_flower_ops,
};
static const struct mlxsw_sp_acl_profile_ops *
mlxsw_sp_acl_tcam_profile_ops(struct mlxsw_sp *mlxsw_sp,
enum mlxsw_sp_acl_profile profile)
{
const struct mlxsw_sp_acl_profile_ops *ops;
if (WARN_ON(profile >= ARRAY_SIZE(mlxsw_sp_acl_tcam_profile_ops_arr)))
return NULL;
ops = mlxsw_sp_acl_tcam_profile_ops_arr[profile];
if (WARN_ON(!ops))
return NULL;
return ops;
}
const struct mlxsw_sp_acl_ops mlxsw_sp_acl_tcam_ops = {
.priv_size = sizeof(struct mlxsw_sp_acl_tcam),
.init = mlxsw_sp_acl_tcam_init,
.fini = mlxsw_sp_acl_tcam_fini,
.profile_ops = mlxsw_sp_acl_tcam_profile_ops,
};
/*
* drivers/net/ethernet/mellanox/mlxsw/spectrum_flower.c
* Copyright (c) 2017 Mellanox Technologies. All rights reserved.
* Copyright (c) 2017 Jiri Pirko <jiri@mellanox.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
* 3. Neither the names of the copyright holders nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* Alternatively, this software may be distributed under the terms of the
* GNU General Public License ("GPL") version 2 as published by the Free
* Software Foundation.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/netdevice.h>
#include <net/flow_dissector.h>
#include <net/pkt_cls.h>
#include <net/tc_act/tc_gact.h>
#include <net/tc_act/tc_mirred.h>
#include "spectrum.h"
#include "core_acl_flex_keys.h"
static int mlxsw_sp_flower_parse_actions(struct mlxsw_sp *mlxsw_sp,
struct net_device *dev,
struct mlxsw_sp_acl_rule_info *rulei,
struct tcf_exts *exts)
{
const struct tc_action *a;
LIST_HEAD(actions);
int err;
if (tc_no_actions(exts))
return 0;
tcf_exts_to_list(exts, &actions);
list_for_each_entry(a, &actions, list) {
if (is_tcf_gact_shot(a)) {
err = mlxsw_sp_acl_rulei_act_drop(rulei);
if (err)
return err;
} else if (is_tcf_mirred_egress_redirect(a)) {
int ifindex = tcf_mirred_ifindex(a);
struct net_device *out_dev;
out_dev = __dev_get_by_index(dev_net(dev), ifindex);
if (out_dev == dev)
out_dev = NULL;
err = mlxsw_sp_acl_rulei_act_fwd(mlxsw_sp, rulei,
out_dev);
if (err)
return err;
} else {
dev_err(mlxsw_sp->bus_info->dev, "Unsupported action\n");
return -EOPNOTSUPP;
}
}
return 0;
}
static void mlxsw_sp_flower_parse_ipv4(struct mlxsw_sp_acl_rule_info *rulei,
struct tc_cls_flower_offload *f)
{
struct flow_dissector_key_ipv4_addrs *key =
skb_flow_dissector_target(f->dissector,
FLOW_DISSECTOR_KEY_IPV4_ADDRS,
f->key);
struct flow_dissector_key_ipv4_addrs *mask =
skb_flow_dissector_target(f->dissector,
FLOW_DISSECTOR_KEY_IPV4_ADDRS,
f->mask);
mlxsw_sp_acl_rulei_keymask_u32(rulei, MLXSW_AFK_ELEMENT_SRC_IP4,
ntohl(key->src), ntohl(mask->src));
mlxsw_sp_acl_rulei_keymask_u32(rulei, MLXSW_AFK_ELEMENT_DST_IP4,
ntohl(key->dst), ntohl(mask->dst));
}
static void mlxsw_sp_flower_parse_ipv6(struct mlxsw_sp_acl_rule_info *rulei,
struct tc_cls_flower_offload *f)
{
struct flow_dissector_key_ipv6_addrs *key =
skb_flow_dissector_target(f->dissector,
FLOW_DISSECTOR_KEY_IPV6_ADDRS,
f->key);
struct flow_dissector_key_ipv6_addrs *mask =
skb_flow_dissector_target(f->dissector,
FLOW_DISSECTOR_KEY_IPV6_ADDRS,
f->mask);
size_t addr_half_size = sizeof(key->src) / 2;
mlxsw_sp_acl_rulei_keymask_buf(rulei, MLXSW_AFK_ELEMENT_SRC_IP6_HI,
&key->src.s6_addr[0],
&mask->src.s6_addr[0],
addr_half_size);
mlxsw_sp_acl_rulei_keymask_buf(rulei, MLXSW_AFK_ELEMENT_SRC_IP6_LO,
&key->src.s6_addr[addr_half_size],
&mask->src.s6_addr[addr_half_size],
addr_half_size);
mlxsw_sp_acl_rulei_keymask_buf(rulei, MLXSW_AFK_ELEMENT_DST_IP6_HI,
&key->dst.s6_addr[0],
&mask->dst.s6_addr[0],
addr_half_size);
mlxsw_sp_acl_rulei_keymask_buf(rulei, MLXSW_AFK_ELEMENT_DST_IP6_LO,
&key->dst.s6_addr[addr_half_size],
&mask->dst.s6_addr[addr_half_size],
addr_half_size);
}
static int mlxsw_sp_flower_parse_ports(struct mlxsw_sp *mlxsw_sp,
struct mlxsw_sp_acl_rule_info *rulei,
struct tc_cls_flower_offload *f,
u8 ip_proto)
{
struct flow_dissector_key_ports *key, *mask;
if (!dissector_uses_key(f->dissector, FLOW_DISSECTOR_KEY_PORTS))
return 0;
if (ip_proto != IPPROTO_TCP && ip_proto != IPPROTO_UDP) {
dev_err(mlxsw_sp->bus_info->dev, "Only UDP and TCP keys are supported\n");
return -EINVAL;
}
key = skb_flow_dissector_target(f->dissector,
FLOW_DISSECTOR_KEY_PORTS,
f->key);
mask = skb_flow_dissector_target(f->dissector,
FLOW_DISSECTOR_KEY_PORTS,
f->mask);
mlxsw_sp_acl_rulei_keymask_u32(rulei, MLXSW_AFK_ELEMENT_DST_L4_PORT,
ntohs(key->dst), ntohs(mask->dst));
mlxsw_sp_acl_rulei_keymask_u32(rulei, MLXSW_AFK_ELEMENT_SRC_L4_PORT,
ntohs(key->src), ntohs(mask->src));
return 0;
}
static int mlxsw_sp_flower_parse(struct mlxsw_sp *mlxsw_sp,
struct net_device *dev,
struct mlxsw_sp_acl_rule_info *rulei,
struct tc_cls_flower_offload *f)
{
u16 addr_type = 0;
u8 ip_proto = 0;
int err;
if (f->dissector->used_keys &
~(BIT(FLOW_DISSECTOR_KEY_CONTROL) |
BIT(FLOW_DISSECTOR_KEY_BASIC) |
BIT(FLOW_DISSECTOR_KEY_ETH_ADDRS) |
BIT(FLOW_DISSECTOR_KEY_IPV4_ADDRS) |
BIT(FLOW_DISSECTOR_KEY_IPV6_ADDRS) |
BIT(FLOW_DISSECTOR_KEY_PORTS))) {
dev_err(mlxsw_sp->bus_info->dev, "Unsupported key\n");
return -EOPNOTSUPP;
}
mlxsw_sp_acl_rulei_priority(rulei, f->prio);
if (dissector_uses_key(f->dissector, FLOW_DISSECTOR_KEY_CONTROL)) {
struct flow_dissector_key_control *key =
skb_flow_dissector_target(f->dissector,
FLOW_DISSECTOR_KEY_CONTROL,
f->key);
addr_type = key->addr_type;
}
if (dissector_uses_key(f->dissector, FLOW_DISSECTOR_KEY_BASIC)) {
struct flow_dissector_key_basic *key =
skb_flow_dissector_target(f->dissector,
FLOW_DISSECTOR_KEY_BASIC,
f->key);
struct flow_dissector_key_basic *mask =
skb_flow_dissector_target(f->dissector,
FLOW_DISSECTOR_KEY_BASIC,
f->mask);
ip_proto = key->ip_proto;
mlxsw_sp_acl_rulei_keymask_u32(rulei,
MLXSW_AFK_ELEMENT_ETHERTYPE,
ntohs(key->n_proto),
ntohs(mask->n_proto));
mlxsw_sp_acl_rulei_keymask_u32(rulei,
MLXSW_AFK_ELEMENT_IP_PROTO,
key->ip_proto, mask->ip_proto);
}
if (dissector_uses_key(f->dissector, FLOW_DISSECTOR_KEY_ETH_ADDRS)) {
struct flow_dissector_key_eth_addrs *key =
skb_flow_dissector_target(f->dissector,
FLOW_DISSECTOR_KEY_ETH_ADDRS,
f->key);
struct flow_dissector_key_eth_addrs *mask =
skb_flow_dissector_target(f->dissector,
FLOW_DISSECTOR_KEY_ETH_ADDRS,
f->mask);
mlxsw_sp_acl_rulei_keymask_buf(rulei,
MLXSW_AFK_ELEMENT_DMAC,
key->dst, mask->dst,
sizeof(key->dst));
mlxsw_sp_acl_rulei_keymask_buf(rulei,
MLXSW_AFK_ELEMENT_SMAC,
key->src, mask->src,
sizeof(key->src));
}
if (addr_type == FLOW_DISSECTOR_KEY_IPV4_ADDRS)
mlxsw_sp_flower_parse_ipv4(rulei, f);
if (addr_type == FLOW_DISSECTOR_KEY_IPV6_ADDRS)
mlxsw_sp_flower_parse_ipv6(rulei, f);
err = mlxsw_sp_flower_parse_ports(mlxsw_sp, rulei, f, ip_proto);
if (err)
return err;
return mlxsw_sp_flower_parse_actions(mlxsw_sp, dev, rulei, f->exts);
}
int mlxsw_sp_flower_replace(struct mlxsw_sp_port *mlxsw_sp_port, bool ingress,
__be16 protocol, struct tc_cls_flower_offload *f)
{
struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
struct net_device *dev = mlxsw_sp_port->dev;
struct mlxsw_sp_acl_rule_info *rulei;
struct mlxsw_sp_acl_ruleset *ruleset;
struct mlxsw_sp_acl_rule *rule;
int err;
ruleset = mlxsw_sp_acl_ruleset_get(mlxsw_sp, dev, ingress,
MLXSW_SP_ACL_PROFILE_FLOWER);
if (IS_ERR(ruleset))
return PTR_ERR(ruleset);
rule = mlxsw_sp_acl_rule_create(mlxsw_sp, ruleset, f->cookie);
if (IS_ERR(rule)) {
err = PTR_ERR(rule);
goto err_rule_create;
}
rulei = mlxsw_sp_acl_rule_rulei(rule);
err = mlxsw_sp_flower_parse(mlxsw_sp, dev, rulei, f);
if (err)
goto err_flower_parse;
err = mlxsw_sp_acl_rulei_commit(rulei);
if (err)
goto err_rulei_commit;
err = mlxsw_sp_acl_rule_add(mlxsw_sp, rule);
if (err)
goto err_rule_add;
mlxsw_sp_acl_ruleset_put(mlxsw_sp, ruleset);
return 0;
err_rule_add:
err_rulei_commit:
err_flower_parse:
mlxsw_sp_acl_rule_destroy(mlxsw_sp, rule);
err_rule_create:
mlxsw_sp_acl_ruleset_put(mlxsw_sp, ruleset);
return err;
}
void mlxsw_sp_flower_destroy(struct mlxsw_sp_port *mlxsw_sp_port, bool ingress,
struct tc_cls_flower_offload *f)
{
struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
struct mlxsw_sp_acl_ruleset *ruleset;
struct mlxsw_sp_acl_rule *rule;
ruleset = mlxsw_sp_acl_ruleset_get(mlxsw_sp, mlxsw_sp_port->dev,
ingress,
MLXSW_SP_ACL_PROFILE_FLOWER);
if (WARN_ON(IS_ERR(ruleset)))
return;
rule = mlxsw_sp_acl_rule_lookup(mlxsw_sp, ruleset, f->cookie);
if (!WARN_ON(!rule)) {
mlxsw_sp_acl_rule_del(mlxsw_sp, rule);
mlxsw_sp_acl_rule_destroy(mlxsw_sp, rule);
}
mlxsw_sp_acl_ruleset_put(mlxsw_sp, ruleset);
}
......@@ -526,6 +526,19 @@ static inline void list_splice_tail_init(struct list_head *list,
for (; &pos->member != (head); \
pos = list_next_entry(pos, member))
/**
* list_for_each_entry_from_reverse - iterate backwards over list of given type
* from the current point
* @pos: the type * to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the list_head within the struct.
*
* Iterate backwards over list of given type, continuing from current position.
*/
#define list_for_each_entry_from_reverse(pos, head, member) \
for (; &pos->member != (head); \
pos = list_prev_entry(pos, member))
/**
* list_for_each_entry_safe - iterate over list of given type safe against removal of list entry
* @pos: the type * to use as a loop cursor.
......
/*
* include/linux/parman.h - Manager for linear priority array areas
* Copyright (c) 2017 Mellanox Technologies. All rights reserved.
* Copyright (c) 2017 Jiri Pirko <jiri@mellanox.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
* 3. Neither the names of the copyright holders nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* Alternatively, this software may be distributed under the terms of the
* GNU General Public License ("GPL") version 2 as published by the Free
* Software Foundation.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef _PARMAN_H
#define _PARMAN_H
#include <linux/list.h>
enum parman_algo_type {
PARMAN_ALGO_TYPE_LSORT,
};
struct parman_item {
struct list_head list;
unsigned long index;
};
struct parman_prio {
struct list_head list;
struct list_head item_list;
unsigned long priority;
};
struct parman_ops {
unsigned long base_count;
unsigned long resize_step;
int (*resize)(void *priv, unsigned long new_count);
void (*move)(void *priv, unsigned long from_index,
unsigned long to_index, unsigned long count);
enum parman_algo_type algo;
};
struct parman;
struct parman *parman_create(const struct parman_ops *ops, void *priv);
void parman_destroy(struct parman *parman);
void parman_prio_init(struct parman *parman, struct parman_prio *prio,
unsigned long priority);
void parman_prio_fini(struct parman_prio *prio);
int parman_item_add(struct parman *parman, struct parman_prio *prio,
struct parman_item *item);
void parman_item_remove(struct parman *parman, struct parman_prio *prio,
struct parman_item *item);
#endif
......@@ -481,6 +481,7 @@ enum tc_fl_command {
struct tc_cls_flower_offload {
enum tc_fl_command command;
u32 prio;
unsigned long cookie;
struct flow_dissector *dissector;
struct fl_flow_key *mask;
......
......@@ -550,4 +550,7 @@ config STACKDEPOT
config SBITMAP
bool
config PARMAN
tristate "parman"
endmenu
......@@ -1826,6 +1826,16 @@ config TEST_HASH
This is intended to help people writing architecture-specific
optimized versions. If unsure, say N.
config TEST_PARMAN
tristate "Perform selftest on priority array manager"
default n
depends on PARMAN
help
Enable this option to test priority array manager on boot
(or module load).
If unsure, say N.
endmenu # runtime tests
config PROVIDE_OHCI1394_DMA_INIT
......
......@@ -56,6 +56,7 @@ obj-$(CONFIG_TEST_STATIC_KEYS) += test_static_key_base.o
obj-$(CONFIG_TEST_PRINTF) += test_printf.o
obj-$(CONFIG_TEST_BITMAP) += test_bitmap.o
obj-$(CONFIG_TEST_UUID) += test_uuid.o
obj-$(CONFIG_TEST_PARMAN) += test_parman.o
ifeq ($(CONFIG_DEBUG_KOBJECT),y)
CFLAGS_kobject.o += -DDEBUG
......@@ -230,3 +231,5 @@ obj-$(CONFIG_UBSAN) += ubsan.o
UBSAN_SANITIZE_ubsan.o := n
obj-$(CONFIG_SBITMAP) += sbitmap.o
obj-$(CONFIG_PARMAN) += parman.o
/*
* lib/parman.c - Manager for linear priority array areas
* Copyright (c) 2017 Mellanox Technologies. All rights reserved.
* Copyright (c) 2017 Jiri Pirko <jiri@mellanox.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
* 3. Neither the names of the copyright holders nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* Alternatively, this software may be distributed under the terms of the
* GNU General Public License ("GPL") version 2 as published by the Free
* Software Foundation.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/export.h>
#include <linux/list.h>
#include <linux/err.h>
#include <linux/parman.h>
struct parman_algo {
int (*item_add)(struct parman *parman, struct parman_prio *prio,
struct parman_item *item);
void (*item_remove)(struct parman *parman, struct parman_prio *prio,
struct parman_item *item);
};
struct parman {
const struct parman_ops *ops;
void *priv;
const struct parman_algo *algo;
unsigned long count;
unsigned long limit_count;
struct list_head prio_list;
};
static int parman_enlarge(struct parman *parman)
{
unsigned long new_count = parman->limit_count +
parman->ops->resize_step;
int err;
err = parman->ops->resize(parman->priv, new_count);
if (err)
return err;
parman->limit_count = new_count;
return 0;
}
static int parman_shrink(struct parman *parman)
{
unsigned long new_count = parman->limit_count -
parman->ops->resize_step;
int err;
if (new_count < parman->ops->base_count)
return 0;
err = parman->ops->resize(parman->priv, new_count);
if (err)
return err;
parman->limit_count = new_count;
return 0;
}
static bool parman_prio_used(struct parman_prio *prio)
{
return !list_empty(&prio->item_list);
}
static struct parman_item *parman_prio_first_item(struct parman_prio *prio)
{
return list_first_entry(&prio->item_list,
typeof(struct parman_item), list);
}
static unsigned long parman_prio_first_index(struct parman_prio *prio)
{
return parman_prio_first_item(prio)->index;
}
static struct parman_item *parman_prio_last_item(struct parman_prio *prio)
{
return list_last_entry(&prio->item_list,
typeof(struct parman_item), list);
}
static unsigned long parman_prio_last_index(struct parman_prio *prio)
{
return parman_prio_last_item(prio)->index;
}
static unsigned long parman_lsort_new_index_find(struct parman *parman,
struct parman_prio *prio)
{
list_for_each_entry_from_reverse(prio, &parman->prio_list, list) {
if (!parman_prio_used(prio))
continue;
return parman_prio_last_index(prio) + 1;
}
return 0;
}
static void __parman_prio_move(struct parman *parman, struct parman_prio *prio,
struct parman_item *item, unsigned long to_index,
unsigned long count)
{
parman->ops->move(parman->priv, item->index, to_index, count);
}
static void parman_prio_shift_down(struct parman *parman,
struct parman_prio *prio)
{
struct parman_item *item;
unsigned long to_index;
if (!parman_prio_used(prio))
return;
item = parman_prio_first_item(prio);
to_index = parman_prio_last_index(prio) + 1;
__parman_prio_move(parman, prio, item, to_index, 1);
list_move_tail(&item->list, &prio->item_list);
item->index = to_index;
}
static void parman_prio_shift_up(struct parman *parman,
struct parman_prio *prio)
{
struct parman_item *item;
unsigned long to_index;
if (!parman_prio_used(prio))
return;
item = parman_prio_last_item(prio);
to_index = parman_prio_first_index(prio) - 1;
__parman_prio_move(parman, prio, item, to_index, 1);
list_move(&item->list, &prio->item_list);
item->index = to_index;
}
static void parman_prio_item_remove(struct parman *parman,
struct parman_prio *prio,
struct parman_item *item)
{
struct parman_item *last_item;
unsigned long to_index;
last_item = parman_prio_last_item(prio);
if (last_item == item) {
list_del(&item->list);
return;
}
to_index = item->index;
__parman_prio_move(parman, prio, last_item, to_index, 1);
list_del(&last_item->list);
list_replace(&item->list, &last_item->list);
last_item->index = to_index;
}
static int parman_lsort_item_add(struct parman *parman,
struct parman_prio *prio,
struct parman_item *item)
{
struct parman_prio *prio2;
unsigned long new_index;
int err;
if (parman->count + 1 > parman->limit_count) {
err = parman_enlarge(parman);
if (err)
return err;
}
new_index = parman_lsort_new_index_find(parman, prio);
list_for_each_entry_reverse(prio2, &parman->prio_list, list) {
if (prio2 == prio)
break;
parman_prio_shift_down(parman, prio2);
}
item->index = new_index;
list_add_tail(&item->list, &prio->item_list);
parman->count++;
return 0;
}
static void parman_lsort_item_remove(struct parman *parman,
struct parman_prio *prio,
struct parman_item *item)
{
parman_prio_item_remove(parman, prio, item);
list_for_each_entry_continue(prio, &parman->prio_list, list)
parman_prio_shift_up(parman, prio);
parman->count--;
if (parman->limit_count - parman->count >= parman->ops->resize_step)
parman_shrink(parman);
}
static const struct parman_algo parman_lsort = {
.item_add = parman_lsort_item_add,
.item_remove = parman_lsort_item_remove,
};
static const struct parman_algo *parman_algos[] = {
&parman_lsort,
};
/**
* parman_create - creates a new parman instance
* @ops: caller-specific callbacks
* @priv: pointer to a private data passed to the ops
*
* Note: all locking must be provided by the caller.
*
* Each parman instance manages an array area with chunks of entries
* with the same priority. Consider following example:
*
* item 1 with prio 10
* item 2 with prio 10
* item 3 with prio 10
* item 4 with prio 20
* item 5 with prio 20
* item 6 with prio 30
* item 7 with prio 30
* item 8 with prio 30
*
* In this example, there are 3 priority chunks. The order of the priorities
* matters, however the order of items within a single priority chunk does not
* matter. So the same array could be ordered as follows:
*
* item 2 with prio 10
* item 3 with prio 10
* item 1 with prio 10
* item 5 with prio 20
* item 4 with prio 20
* item 7 with prio 30
* item 8 with prio 30
* item 6 with prio 30
*
* The goal of parman is to maintain the priority ordering. The caller
* provides @ops with callbacks parman uses to move the items
* and resize the array area.
*
* Returns a pointer to newly created parman instance in case of success,
* otherwise it returns NULL.
*/
struct parman *parman_create(const struct parman_ops *ops, void *priv)
{
struct parman *parman;
parman = kzalloc(sizeof(*parman), GFP_KERNEL);
if (!parman)
return NULL;
INIT_LIST_HEAD(&parman->prio_list);
parman->ops = ops;
parman->priv = priv;
parman->limit_count = ops->base_count;
parman->algo = parman_algos[ops->algo];
return parman;
}
EXPORT_SYMBOL(parman_create);
/**
* parman_destroy - destroys existing parman instance
* @parman: parman instance
*
* Note: all locking must be provided by the caller.
*/
void parman_destroy(struct parman *parman)
{
WARN_ON(!list_empty(&parman->prio_list));
kfree(parman);
}
EXPORT_SYMBOL(parman_destroy);
/**
* parman_prio_init - initializes a parman priority chunk
* @parman: parman instance
* @prio: parman prio structure to be initialized
* @prority: desired priority of the chunk
*
* Note: all locking must be provided by the caller.
*
* Before caller could add an item with certain priority, he has to
* initialize a priority chunk for it using this function.
*/
void parman_prio_init(struct parman *parman, struct parman_prio *prio,
unsigned long priority)
{
struct parman_prio *prio2;
struct list_head *pos;
INIT_LIST_HEAD(&prio->item_list);
prio->priority = priority;
/* Position inside the list according to priority */
list_for_each(pos, &parman->prio_list) {
prio2 = list_entry(pos, typeof(*prio2), list);
if (prio2->priority > prio->priority)
break;
}
list_add_tail(&prio->list, pos);
}
EXPORT_SYMBOL(parman_prio_init);
/**
* parman_prio_fini - finalizes use of parman priority chunk
* @prio: parman prio structure
*
* Note: all locking must be provided by the caller.
*/
void parman_prio_fini(struct parman_prio *prio)
{
WARN_ON(parman_prio_used(prio));
list_del(&prio->list);
}
EXPORT_SYMBOL(parman_prio_fini);
/**
* parman_item_add - adds a parman item under defined priority
* @parman: parman instance
* @prio: parman prio instance to add the item to
* @item: parman item instance
*
* Note: all locking must be provided by the caller.
*
* Adds item to a array managed by parman instance under the specified priority.
*
* Returns 0 in case of success, negative number to indicate an error.
*/
int parman_item_add(struct parman *parman, struct parman_prio *prio,
struct parman_item *item)
{
return parman->algo->item_add(parman, prio, item);
}
EXPORT_SYMBOL(parman_item_add);
/**
* parman_item_del - deletes parman item
* @parman: parman instance
* @prio: parman prio instance to delete the item from
* @item: parman item instance
*
* Note: all locking must be provided by the caller.
*/
void parman_item_remove(struct parman *parman, struct parman_prio *prio,
struct parman_item *item)
{
parman->algo->item_remove(parman, prio, item);
}
EXPORT_SYMBOL(parman_item_remove);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Jiri Pirko <jiri@mellanox.com>");
MODULE_DESCRIPTION("Priority-based array manager");
/*
* lib/test_parman.c - Test module for parman
* Copyright (c) 2017 Mellanox Technologies. All rights reserved.
* Copyright (c) 2017 Jiri Pirko <jiri@mellanox.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
* 3. Neither the names of the copyright holders nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* Alternatively, this software may be distributed under the terms of the
* GNU General Public License ("GPL") version 2 as published by the Free
* Software Foundation.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/bitops.h>
#include <linux/err.h>
#include <linux/random.h>
#include <linux/parman.h>
#define TEST_PARMAN_PRIO_SHIFT 7 /* defines number of prios for testing */
#define TEST_PARMAN_PRIO_COUNT BIT(TEST_PARMAN_PRIO_SHIFT)
#define TEST_PARMAN_PRIO_MASK (TEST_PARMAN_PRIO_COUNT - 1)
#define TEST_PARMAN_ITEM_SHIFT 13 /* defines a total number
* of items for testing
*/
#define TEST_PARMAN_ITEM_COUNT BIT(TEST_PARMAN_ITEM_SHIFT)
#define TEST_PARMAN_ITEM_MASK (TEST_PARMAN_ITEM_COUNT - 1)
#define TEST_PARMAN_BASE_SHIFT 8
#define TEST_PARMAN_BASE_COUNT BIT(TEST_PARMAN_BASE_SHIFT)
#define TEST_PARMAN_RESIZE_STEP_SHIFT 7
#define TEST_PARMAN_RESIZE_STEP_COUNT BIT(TEST_PARMAN_RESIZE_STEP_SHIFT)
#define TEST_PARMAN_BULK_MAX_SHIFT (2 + TEST_PARMAN_RESIZE_STEP_SHIFT)
#define TEST_PARMAN_BULK_MAX_COUNT BIT(TEST_PARMAN_BULK_MAX_SHIFT)
#define TEST_PARMAN_BULK_MAX_MASK (TEST_PARMAN_BULK_MAX_COUNT - 1)
#define TEST_PARMAN_RUN_BUDGET (TEST_PARMAN_ITEM_COUNT * 256)
struct test_parman_prio {
struct parman_prio parman_prio;
unsigned long priority;
};
struct test_parman_item {
struct parman_item parman_item;
struct test_parman_prio *prio;
bool used;
};
struct test_parman {
struct parman *parman;
struct test_parman_item **prio_array;
unsigned long prio_array_limit;
struct test_parman_prio prios[TEST_PARMAN_PRIO_COUNT];
struct test_parman_item items[TEST_PARMAN_ITEM_COUNT];
struct rnd_state rnd;
unsigned long run_budget;
unsigned long bulk_budget;
bool bulk_noop;
unsigned int used_items;
};
#define ITEM_PTRS_SIZE(count) (sizeof(struct test_parman_item *) * (count))
static int test_parman_resize(void *priv, unsigned long new_count)
{
struct test_parman *test_parman = priv;
struct test_parman_item **prio_array;
unsigned long old_count;
prio_array = krealloc(test_parman->prio_array,
ITEM_PTRS_SIZE(new_count), GFP_KERNEL);
if (new_count == 0)
return 0;
if (!prio_array)
return -ENOMEM;
old_count = test_parman->prio_array_limit;
if (new_count > old_count)
memset(&prio_array[old_count], 0,
ITEM_PTRS_SIZE(new_count - old_count));
test_parman->prio_array = prio_array;
test_parman->prio_array_limit = new_count;
return 0;
}
static void test_parman_move(void *priv, unsigned long from_index,
unsigned long to_index, unsigned long count)
{
struct test_parman *test_parman = priv;
struct test_parman_item **prio_array = test_parman->prio_array;
memmove(&prio_array[to_index], &prio_array[from_index],
ITEM_PTRS_SIZE(count));
memset(&prio_array[from_index], 0, ITEM_PTRS_SIZE(count));
}
static const struct parman_ops test_parman_lsort_ops = {
.base_count = TEST_PARMAN_BASE_COUNT,
.resize_step = TEST_PARMAN_RESIZE_STEP_COUNT,
.resize = test_parman_resize,
.move = test_parman_move,
.algo = PARMAN_ALGO_TYPE_LSORT,
};
static void test_parman_rnd_init(struct test_parman *test_parman)
{
prandom_seed_state(&test_parman->rnd, 3141592653589793238ULL);
}
static u32 test_parman_rnd_get(struct test_parman *test_parman)
{
return prandom_u32_state(&test_parman->rnd);
}
static unsigned long test_parman_priority_gen(struct test_parman *test_parman)
{
unsigned long priority;
int i;
again:
priority = test_parman_rnd_get(test_parman);
if (priority == 0)
goto again;
for (i = 0; i < TEST_PARMAN_PRIO_COUNT; i++) {
struct test_parman_prio *prio = &test_parman->prios[i];
if (prio->priority == 0)
break;
if (prio->priority == priority)
goto again;
}
return priority;
}
static void test_parman_prios_init(struct test_parman *test_parman)
{
int i;
for (i = 0; i < TEST_PARMAN_PRIO_COUNT; i++) {
struct test_parman_prio *prio = &test_parman->prios[i];
/* Assign random uniqueue priority to each prio structure */
prio->priority = test_parman_priority_gen(test_parman);
parman_prio_init(test_parman->parman, &prio->parman_prio,
prio->priority);
}
}
static void test_parman_prios_fini(struct test_parman *test_parman)
{
int i;
for (i = 0; i < TEST_PARMAN_PRIO_COUNT; i++) {
struct test_parman_prio *prio = &test_parman->prios[i];
parman_prio_fini(&prio->parman_prio);
}
}
static void test_parman_items_init(struct test_parman *test_parman)
{
int i;
for (i = 0; i < TEST_PARMAN_ITEM_COUNT; i++) {
struct test_parman_item *item = &test_parman->items[i];
unsigned int prio_index = test_parman_rnd_get(test_parman) &
TEST_PARMAN_PRIO_MASK;
/* Assign random prio to each item structure */
item->prio = &test_parman->prios[prio_index];
}
}
static void test_parman_items_fini(struct test_parman *test_parman)
{
int i;
for (i = 0; i < TEST_PARMAN_ITEM_COUNT; i++) {
struct test_parman_item *item = &test_parman->items[i];
if (!item->used)
continue;
parman_item_remove(test_parman->parman,
&item->prio->parman_prio,
&item->parman_item);
}
}
static struct test_parman *test_parman_create(const struct parman_ops *ops)
{
struct test_parman *test_parman;
int err;
test_parman = kzalloc(sizeof(*test_parman), GFP_KERNEL);
if (!test_parman)
return ERR_PTR(-ENOMEM);
err = test_parman_resize(test_parman, TEST_PARMAN_BASE_COUNT);
if (err)
goto err_resize;
test_parman->parman = parman_create(ops, test_parman);
if (!test_parman->parman) {
err = -ENOMEM;
goto err_parman_create;
}
test_parman_rnd_init(test_parman);
test_parman_prios_init(test_parman);
test_parman_items_init(test_parman);
test_parman->run_budget = TEST_PARMAN_RUN_BUDGET;
return test_parman;
err_parman_create:
test_parman_resize(test_parman, 0);
err_resize:
kfree(test_parman);
return ERR_PTR(err);
}
static void test_parman_destroy(struct test_parman *test_parman)
{
test_parman_items_fini(test_parman);
test_parman_prios_fini(test_parman);
parman_destroy(test_parman->parman);
test_parman_resize(test_parman, 0);
kfree(test_parman);
}
static bool test_parman_run_check_budgets(struct test_parman *test_parman)
{
if (test_parman->run_budget-- == 0)
return false;
if (test_parman->bulk_budget-- != 0)
return true;
test_parman->bulk_budget = test_parman_rnd_get(test_parman) &
TEST_PARMAN_BULK_MAX_MASK;
test_parman->bulk_noop = test_parman_rnd_get(test_parman) & 1;
return true;
}
static int test_parman_run(struct test_parman *test_parman)
{
unsigned int i = test_parman_rnd_get(test_parman);
int err;
while (test_parman_run_check_budgets(test_parman)) {
unsigned int item_index = i++ & TEST_PARMAN_ITEM_MASK;
struct test_parman_item *item = &test_parman->items[item_index];
if (test_parman->bulk_noop)
continue;
if (!item->used) {
err = parman_item_add(test_parman->parman,
&item->prio->parman_prio,
&item->parman_item);
if (err)
return err;
test_parman->prio_array[item->parman_item.index] = item;
test_parman->used_items++;
} else {
test_parman->prio_array[item->parman_item.index] = NULL;
parman_item_remove(test_parman->parman,
&item->prio->parman_prio,
&item->parman_item);
test_parman->used_items--;
}
item->used = !item->used;
}
return 0;
}
static int test_parman_check_array(struct test_parman *test_parman,
bool gaps_allowed)
{
unsigned int last_unused_items = 0;
unsigned long last_priority = 0;
unsigned int used_items = 0;
int i;
if (test_parman->prio_array_limit < TEST_PARMAN_BASE_COUNT) {
pr_err("Array limit is lower than the base count (%lu < %lu)\n",
test_parman->prio_array_limit, TEST_PARMAN_BASE_COUNT);
return -EINVAL;
}
for (i = 0; i < test_parman->prio_array_limit; i++) {
struct test_parman_item *item = test_parman->prio_array[i];
if (!item) {
last_unused_items++;
continue;
}
if (last_unused_items && !gaps_allowed) {
pr_err("Gap found in array even though they are forbidden\n");
return -EINVAL;
}
last_unused_items = 0;
used_items++;
if (item->prio->priority < last_priority) {
pr_err("Item belongs under higher priority then the last one (current: %lu, previous: %lu)\n",
item->prio->priority, last_priority);
return -EINVAL;
}
last_priority = item->prio->priority;
if (item->parman_item.index != i) {
pr_err("Item has different index in compare to where it actualy is (%lu != %d)\n",
item->parman_item.index, i);
return -EINVAL;
}
}
if (used_items != test_parman->used_items) {
pr_err("Number of used items in array does not match (%u != %u)\n",
used_items, test_parman->used_items);
return -EINVAL;
}
if (last_unused_items >= TEST_PARMAN_RESIZE_STEP_COUNT) {
pr_err("Number of unused item at the end of array is bigger than resize step (%u >= %lu)\n",
last_unused_items, TEST_PARMAN_RESIZE_STEP_COUNT);
return -EINVAL;
}
pr_info("Priority array check successful\n");
return 0;
}
static int test_parman_lsort(void)
{
struct test_parman *test_parman;
int err;
test_parman = test_parman_create(&test_parman_lsort_ops);
if (IS_ERR(test_parman))
return PTR_ERR(test_parman);
err = test_parman_run(test_parman);
if (err)
goto out;
err = test_parman_check_array(test_parman, false);
if (err)
goto out;
out:
test_parman_destroy(test_parman);
return err;
}
static int __init test_parman_init(void)
{
return test_parman_lsort();
}
static void __exit test_parman_exit(void)
{
}
module_init(test_parman_init);
module_exit(test_parman_exit);
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("Jiri Pirko <jiri@mellanox.com>");
MODULE_DESCRIPTION("Test module for parman");
......@@ -229,6 +229,7 @@ static void fl_hw_destroy_filter(struct tcf_proto *tp, struct cls_fl_filter *f)
return;
offload.command = TC_CLSFLOWER_DESTROY;
offload.prio = tp->prio;
offload.cookie = (unsigned long)f;
tc->type = TC_SETUP_CLSFLOWER;
......@@ -260,6 +261,7 @@ static int fl_hw_replace_filter(struct tcf_proto *tp,
}
offload.command = TC_CLSFLOWER_REPLACE;
offload.prio = tp->prio;
offload.cookie = (unsigned long)f;
offload.dissector = dissector;
offload.mask = mask;
......@@ -287,6 +289,7 @@ static void fl_hw_update_stats(struct tcf_proto *tp, struct cls_fl_filter *f)
return;
offload.command = TC_CLSFLOWER_STATS;
offload.prio = tp->prio;
offload.cookie = (unsigned long)f;
offload.exts = &f->exts;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment