blob: cc395a996f039ab9f312754c16aefaf541fe4fb9 [file] [log] [blame]
/*
*******************************************************************************
* Copyright (c) 2020, The Linux Foundation. All rights reserved.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
********************************************************************************
**/
#include "nss_match_db.h"
#include "nss_match_priv.h"
#define NSS_MATCH_MAX_DSCP 63
#define NSS_MATCH_MAX_8021P 7
#define NSS_MATCH_VOW_KEY_IFNUM_SHIFT 0
#define NSS_MATCH_VOW_KEY_DSCP_SHIFT 16
#define NSS_MATCH_VOW_KEY_INNER_8021P_SHIFT 22
#define NSS_MATCH_VOW_KEY_OUTER_8021P_SHIFT 25
/*
* nss_match_vow_rule_find()
* Check if rule exists already.
*/
static bool nss_match_vow_rule_find(struct nss_match_instance *db_instance, struct nss_match_rule_info *rule)
{
uint16_t index;
/*
* Check if entry is present already.
*/
for (index = 0; index < NSS_MATCH_INSTANCE_RULE_MAX; index++) {
struct nss_match_rule_info rule_info = db_instance->rules[index];
if (rule->valid_rule &&
rule_info.profile.vow.if_num == rule->profile.vow.if_num &&
rule_info.profile.vow.dscp == rule->profile.vow.dscp &&
rule_info.profile.vow.inner_8021p == rule->profile.vow.inner_8021p &&
rule_info.profile.vow.outer_8021p == rule->profile.vow.outer_8021p &&
rule_info.profile.vow.mask_id == rule->profile.vow.mask_id) {
nss_match_info("Rule matched.\n");
return true;
}
}
return false;
}
/*
* nss_match_vow_db_rule_add()
* Store VoW rule information.
*/
static bool nss_match_vow_db_rule_add(struct nss_match_instance *db_instance, struct nss_match_rule_info *rule)
{
uint8_t rule_id = rule->profile.vow.rule_id;
uint8_t mask_id = rule->profile.vow.mask_id;
if (rule_id == 0 || rule_id > NSS_MATCH_INSTANCE_RULE_MAX) {
nss_match_info("Invalid rule id: %d\n", rule_id);
return false;
}
if (db_instance->rules[rule_id - 1].valid_rule) {
nss_match_info("Rule exists for rule id: %d\n", rule_id);
return false;
}
db_instance->rules[rule_id - 1].profile.vow.if_num = rule->profile.vow.if_num;
db_instance->rules[rule_id - 1].profile.vow.dscp = rule->profile.vow.dscp;
db_instance->rules[rule_id - 1].profile.vow.inner_8021p = rule->profile.vow.inner_8021p;
db_instance->rules[rule_id - 1].profile.vow.outer_8021p = rule->profile.vow.outer_8021p;
db_instance->rules[rule_id - 1].profile.vow.mask_id = mask_id;
db_instance->rules[rule_id - 1].profile.vow.action.action_flag = rule->profile.vow.action.action_flag;
db_instance->rules[rule_id - 1].profile.vow.action.setprio = rule->profile.vow.action.setprio;
db_instance->rules[rule_id - 1].profile.vow.action.forward_ifnum = rule->profile.vow.action.forward_ifnum;
db_instance->rules[rule_id - 1].valid_rule = true;
db_instance->rules[rule_id - 1].profile.vow.rule_id = rule_id;
db_instance->valid_rule_mask[mask_id - 1][rule_id - 1] = true;
db_instance->rule_count++;
return true;
}
/*
* nss_match_vow_rule_read()
* Reads rule parameters by rule id.
*/
static bool nss_match_vow_rule_read(struct nss_match_instance *db_instance, struct nss_match_rule_info *rule, uint16_t rule_id)
{
if (rule_id == 0 || rule_id > NSS_MATCH_INSTANCE_RULE_MAX) {
nss_match_info("Invalid rule id: %d\n", rule_id);
return false;
}
if (!db_instance->rules[rule_id - 1].valid_rule) {
nss_match_info("rule_id doesnot exist, rule_id = %d", rule_id);
return false;
}
rule->profile.vow.if_num = db_instance->rules[rule_id - 1].profile.vow.if_num;
rule->profile.vow.dscp = db_instance->rules[rule_id - 1].profile.vow.dscp;
rule->profile.vow.outer_8021p = db_instance->rules[rule_id - 1].profile.vow.outer_8021p;
rule->profile.vow.inner_8021p = db_instance->rules[rule_id - 1].profile.vow.inner_8021p;
rule->profile.vow.mask_id = db_instance->rules[rule_id - 1].profile.vow.mask_id;
rule->profile.vow.rule_id = db_instance->rules[rule_id - 1].profile.vow.rule_id;
return true;
}
/*
* nss_match_vow_cmd_parse()
* Adds new rules to the list
*/
static int nss_match_vow_cmd_parse(char *input_msg, struct nss_match_msg *rule_msg, nss_match_cmd_t type)
{
char *token, *param, *value;
struct nss_ctx_instance *nss_ctx = nss_match_get_context();
int ret = 0;
uint32_t actions = 0, if_num = 0, dscp = 0, outer_prio = 0, inner_prio = 0, setprio = 0, nexthop = 0;
uint16_t mask_id = 0;
uint32_t mask_val = 0;
/*
* Parse the user input.
*/
while (input_msg != NULL) {
token = strsep(&input_msg, " ");
param = strsep(&token, "=");
value = token;
if (!param || !value) {
goto fail;
}
/*
* Parsing mask ID from the message.
*/
if (!(strncasecmp(param, "mask", strlen("mask")))) {
if (!sscanf(value, "%hu", &mask_id)) {
pr_info("%px: Cannot convert to integer. Wrong input\n", nss_ctx);
return -EINVAL;
}
continue;
}
/*
* Parsing interface name from the message.
*/
if (!(strncasecmp(param, "ifname", strlen("ifname")))) {
struct net_device *dev;
if (type == NSS_MATCH_ADD_MASK) {
if (!sscanf(value, "%x", &if_num)) {
pr_info("%px: Cannot convert to integer. Wrong input\n", nss_ctx);
return -EINVAL;
}
continue;
}
if (type == NSS_MATCH_ADD_RULE) {
dev = dev_get_by_name(&init_net, value);
if (!dev) {
pr_info("%px: Cannot find the net device\n", nss_ctx);
return -ENODEV;
}
if_num = nss_cmn_get_interface_number_by_dev(dev);
dev_put(dev);
continue;
}
}
/*
* Parsing Dscp value from the message.
*/
if (!(strncasecmp(param, "dscp", strlen("dscp")))) {
if (type == NSS_MATCH_ADD_RULE) {
ret = sscanf(value, "%u", &dscp);
} else if (type == NSS_MATCH_ADD_MASK) {
ret = sscanf(value, "%x", &dscp);
}
if (!ret) {
pr_info("%px: Cannot convert to integer. Wrong input\n", nss_ctx);
return -EINVAL;
}
continue;
}
/*
* Parsing 8021.p inner from the message given by host.
*/
if (!(strncasecmp(param, "802.1p_inner", strlen("802.1p_inner")))) {
if (type == NSS_MATCH_ADD_RULE) {
ret = sscanf(value, "%u", &inner_prio);
} else if (type == NSS_MATCH_ADD_MASK) {
ret = sscanf(value, "%x", &inner_prio);
}
if (!ret) {
pr_info("%px: Cannot convert to integer. Wrong input!!\n", nss_ctx);
return -EINVAL;
}
continue;
}
/*
* Parsing 8021.p outer from the message given by host.
*/
if (!(strncasecmp(param, "802.1p_outer", strlen("802.1p_outer")))) {
if (type == NSS_MATCH_ADD_RULE) {
ret = sscanf(value, "%u", &outer_prio);
} else if (type == NSS_MATCH_ADD_MASK) {
ret = sscanf(value, "%x", &outer_prio);
}
if (!ret) {
pr_info("%px: Cannot convert to integer. Wrong input\n", nss_ctx);
return -EINVAL;
}
continue;
}
/*
* Parsing action from the message provided by user.
*/
if (!(strncasecmp(param, "action", strlen("action")))) {
if (!sscanf(value, "%u", &actions)) {
pr_info("%px: Cannot convert to integer. Wrong input\n", nss_ctx);
return -EINVAL;
}
continue;
}
/*
* Parsing priority for action provided by user.
*/
if (!(strncasecmp(param, "priority", strlen("priority")))) {
if (!sscanf(value, "%u", &setprio)) {
pr_info("%px: Cannot convert to integer. Wrong input\n", nss_ctx);
return -EINVAL;
}
continue;
}
/*
* Parsing nexthop for action provided by user.
*/
if (!(strncasecmp(param, "nexthop", strlen("nexthop")))) {
if (!sscanf(value, "%u", &nexthop)) {
pr_info("%px: Cannot convert to integer. Wrong input\n", nss_ctx);
return -EINVAL;
}
continue;
}
pr_info("%px: Not a valid input\n", nss_ctx);
return -EINVAL;
}
/*
* Validate user input values.
*/
if (mask_id > NSS_MATCH_MASK_MAX) {
pr_info("%px: Maskset num exceeds allowed value: %d\n", nss_ctx, mask_id);
return -EINVAL;
}
if (dscp > NSS_MATCH_MAX_DSCP) {
pr_info("%px: Dscp value %d cannot go beyond %d\n", nss_ctx, dscp, NSS_MATCH_MAX_DSCP);
return -EINVAL;
}
if (inner_prio > NSS_MATCH_MAX_8021P || outer_prio > NSS_MATCH_MAX_8021P) {
pr_info("%px: Priority inner:%d outer:%d value cannot go beyond 7.\n", nss_ctx, inner_prio, outer_prio);
return -EINVAL;
}
/*
* Verify correctness of field combination.
*/
switch (type) {
case NSS_MATCH_ADD_RULE:
if (!mask_id || !actions) {
goto fail;
}
switch(actions) {
case NSS_MATCH_ACTION_SETPRIO:
if (nexthop || !setprio || setprio >= NSS_MAX_NUM_PRI) {
goto fail;
}
break;
case NSS_MATCH_ACTION_FORWARD:
if (setprio || !nexthop) {
goto fail;
}
break;
case NSS_MATCH_ACTION_SETPRIO | NSS_MATCH_ACTION_FORWARD:
if (!setprio || !nexthop || setprio >= NSS_MAX_NUM_PRI) {
goto fail;
}
break;
case NSS_MATCH_ACTION_DROP:
if (setprio || nexthop) {
goto fail;
}
break;
default:
goto fail;
}
if (!outer_prio && inner_prio) {
pr_info("Wrong config for two layer of vlan, inner = %u outer = %u", inner_prio, outer_prio);
goto fail;
}
rule_msg->msg.vow_rule.if_num = if_num;
rule_msg->msg.vow_rule.dscp = dscp;
rule_msg->msg.vow_rule.outer_8021p = outer_prio;
rule_msg->msg.vow_rule.inner_8021p = inner_prio;
rule_msg->msg.vow_rule.mask_id = mask_id;
rule_msg->msg.vow_rule.action.setprio = setprio;
rule_msg->msg.vow_rule.action.action_flag = actions;
rule_msg->msg.vow_rule.action.forward_ifnum = nexthop;
break;
case NSS_MATCH_ADD_MASK:
if (!mask_id) {
pr_info("Missing mandatory field, mask ID.\n");
goto fail;
}
mask_val = (if_num << NSS_MATCH_VOW_KEY_IFNUM_SHIFT);
mask_val |= (dscp << NSS_MATCH_VOW_KEY_DSCP_SHIFT);
mask_val |= (inner_prio << NSS_MATCH_VOW_KEY_INNER_8021P_SHIFT);
mask_val |= (outer_prio << NSS_MATCH_VOW_KEY_OUTER_8021P_SHIFT);
rule_msg->msg.configure_msg.profile_type = NSS_MATCH_PROFILE_TYPE_VOW;
rule_msg->msg.configure_msg.maskset[mask_id-1][0] = mask_val;
rule_msg->msg.configure_msg.valid_mask_flag = mask_id;
break;
default:
pr_info("Invalid parse type: %d", type);
goto fail;
}
return 0;
fail:
pr_warn("Invalid input, Check help.(cat /proc/sys/dev/nss/match/help)");
return -EINVAL;
}
/*
* nss_match_vow_table_read()
* Reads stats for VoW profile.
*/
static size_t nss_match_vow_table_read(struct nss_match_instance *db_instance, size_t buflen, char *bufp)
{
int i, j;
struct nss_ctx_instance *nss_ctx = nss_match_get_context();
struct net_device *net_dev;
uint64_t mask_hit_count = 0;
size_t size_wr = 0;
char *dev_name;
size_wr += scnprintf(bufp + size_wr, buflen - size_wr, "Match if_num = %d\n", db_instance->if_num);
size_wr += scnprintf(bufp + size_wr, buflen - size_wr, "Profile Type = %d\n", db_instance->profile_type);
for (i = 0; i < NSS_MATCH_MASK_MAX; i++) {
if (!(db_instance->valid_mask_flag & (1 << i))) {
size_wr += scnprintf(bufp + size_wr, buflen - size_wr, "Mask %d = Invalid\n", i+1);
continue;
}
for (j = 0; j < NSS_MATCH_INSTANCE_RULE_MAX; j++) {
if (!db_instance->valid_rule_mask[i][j]) {
continue;
}
mask_hit_count += db_instance->stats.hit_count[j];
}
size_wr += scnprintf(bufp + size_wr, buflen - size_wr, "Mask %d = %x\t Rule hits using mask %d = %llu\n",
i+1, db_instance->maskset[i][0], i+1, mask_hit_count);
mask_hit_count = 0;
}
size_wr += scnprintf(bufp + size_wr, buflen - size_wr, "%8s %20s %8s %8s %8s %9s %8s %8s %8s %9s\n\n",
"rule_id", "hit count per rule", "mask_id", "if_name", "dscp", "out_vlan", "in_vlan", "action", "prio", "nh_ifnum");
for (j = 0; j < NSS_MATCH_INSTANCE_RULE_MAX; j++) {
if (!db_instance->rules[j].valid_rule)
continue;
dev_name = "N/A";
net_dev = nss_cmn_get_interface_dev(nss_ctx, db_instance->rules[j].profile.vow.if_num);
if (net_dev) {
dev_name = net_dev->name;
}
size_wr += scnprintf(bufp + size_wr, buflen - size_wr, "%8d %20llu %8d %8s %8d %8d %8d %8u %8d %8d\n",
j+1,
db_instance->stats.hit_count[j],
db_instance->rules[j].profile.vow.mask_id,
dev_name,
db_instance->rules[j].profile.vow.dscp,
db_instance->rules[j].profile.vow.outer_8021p,
db_instance->rules[j].profile.vow.inner_8021p,
db_instance->rules[j].profile.vow.action.action_flag,
db_instance->rules[j].profile.vow.action.setprio,
db_instance->rules[j].profile.vow.action.forward_ifnum);
}
return size_wr;
}
/*
* Match ops for VoW profile.
*/
static struct match_profile_ops match_profile_vow_ops = {
nss_match_vow_rule_find,
nss_match_vow_db_rule_add,
nss_match_vow_rule_read,
nss_match_vow_table_read,
nss_match_vow_cmd_parse,
};
/*
* nss_match_vow_init()
* Initializes the VoW profile.
*/
void nss_match_vow_init(void) {
/*
* Register the VoW profile ops.
*/
nss_match_profile_ops_register(NSS_MATCH_PROFILE_TYPE_VOW, &match_profile_vow_ops);
nss_match_info("VoW profile initialization done\n");
}