Type: feature Change-Id: I3485e1627eafc5125255985003573247e7562db2 Signed-off-by: Kommula Shiva Shankar <kshankar@marvell.com> Signed-off-by: Monendra Singh Kushwaha <kmonendra@marvell.com>
506 lines
16 KiB
C
506 lines
16 KiB
C
/*
|
|
* Copyright (c) 2024 Marvell.
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
* https://spdx.org/licenses/Apache-2.0.html
|
|
*/
|
|
|
|
#include <dev_octeon/octeon.h>
|
|
#include <base/roc_npc_priv.h>
|
|
|
|
VLIB_REGISTER_LOG_CLASS (oct_log, static) = {
|
|
.class_name = "octeon",
|
|
.subclass_name = "flow",
|
|
};
|
|
|
|
#define FLOW_IS_ETHERNET_CLASS(f) (f->type == VNET_FLOW_TYPE_ETHERNET)
|
|
|
|
#define FLOW_IS_IPV4_CLASS(f) \
|
|
((f->type == VNET_FLOW_TYPE_IP4) || \
|
|
(f->type == VNET_FLOW_TYPE_IP4_N_TUPLE) || \
|
|
(f->type == VNET_FLOW_TYPE_IP4_N_TUPLE_TAGGED) || \
|
|
(f->type == VNET_FLOW_TYPE_IP4_VXLAN) || \
|
|
(f->type == VNET_FLOW_TYPE_IP4_GTPC) || \
|
|
(f->type == VNET_FLOW_TYPE_IP4_GTPU) || \
|
|
(f->type == VNET_FLOW_TYPE_IP4_L2TPV3OIP) || \
|
|
(f->type == VNET_FLOW_TYPE_IP4_IPSEC_ESP) || \
|
|
(f->type == VNET_FLOW_TYPE_IP4_IPSEC_AH))
|
|
|
|
#define FLOW_IS_IPV6_CLASS(f) \
|
|
((f->type == VNET_FLOW_TYPE_IP6) || \
|
|
(f->type == VNET_FLOW_TYPE_IP6_N_TUPLE) || \
|
|
(f->type == VNET_FLOW_TYPE_IP6_N_TUPLE_TAGGED) || \
|
|
(f->type == VNET_FLOW_TYPE_IP6_VXLAN))
|
|
|
|
#define FLOW_IS_L3_TYPE(f) \
|
|
((f->type == VNET_FLOW_TYPE_IP4) || (f->type == VNET_FLOW_TYPE_IP6))
|
|
|
|
#define FLOW_IS_L4_TYPE(f) \
|
|
((f->type == VNET_FLOW_TYPE_IP4_N_TUPLE) || \
|
|
(f->type == VNET_FLOW_TYPE_IP6_N_TUPLE) || \
|
|
(f->type == VNET_FLOW_TYPE_IP4_N_TUPLE_TAGGED) || \
|
|
(f->type == VNET_FLOW_TYPE_IP6_N_TUPLE_TAGGED))
|
|
|
|
#define FLOW_IS_L4_TUNNEL_TYPE(f) \
|
|
((f->type == VNET_FLOW_TYPE_IP4_VXLAN) || \
|
|
(f->type == VNET_FLOW_TYPE_IP6_VXLAN) || \
|
|
(f->type == VNET_FLOW_TYPE_IP4_GTPC) || \
|
|
(f->type == VNET_FLOW_TYPE_IP4_GTPU))
|
|
|
|
#define OCT_FLOW_UNSUPPORTED_ACTIONS(f) \
|
|
((f->actions == VNET_FLOW_ACTION_BUFFER_ADVANCE) || \
|
|
(f->actions == VNET_FLOW_ACTION_REDIRECT_TO_NODE))
|
|
|
|
/* Keep values in sync with vnet/flow.h */
|
|
#define foreach_oct_flow_rss_types \
|
|
_ (1, FLOW_KEY_TYPE_IPV4 | FLOW_KEY_TYPE_TCP, "ipv4-tcp") \
|
|
_ (2, FLOW_KEY_TYPE_IPV4 | FLOW_KEY_TYPE_UDP, "ipv4-udp") \
|
|
_ (3, FLOW_KEY_TYPE_IPV4 | FLOW_KEY_TYPE_SCTP, "ipv4-sctp") \
|
|
_ (5, FLOW_KEY_TYPE_IPV4, "ipv4") \
|
|
_ (9, FLOW_KEY_TYPE_IPV6 | FLOW_KEY_TYPE_TCP, "ipv6-tcp") \
|
|
_ (10, FLOW_KEY_TYPE_IPV6 | FLOW_KEY_TYPE_UDP, "ipv6-udp") \
|
|
_ (11, FLOW_KEY_TYPE_IPV6 | FLOW_KEY_TYPE_SCTP, "ipv6-sctp") \
|
|
_ (13, FLOW_KEY_TYPE_IPV6_EXT, "ipv6-ex") \
|
|
_ (14, FLOW_KEY_TYPE_IPV6, "ipv6") \
|
|
_ (16, FLOW_KEY_TYPE_PORT, "port") \
|
|
_ (17, FLOW_KEY_TYPE_VXLAN, "vxlan") \
|
|
_ (18, FLOW_KEY_TYPE_GENEVE, "geneve") \
|
|
_ (19, FLOW_KEY_TYPE_NVGRE, "nvgre") \
|
|
_ (20, FLOW_KEY_TYPE_GTPU, "gtpu") \
|
|
_ (60, FLOW_KEY_TYPE_L4_DST, "l4-dst-only") \
|
|
_ (61, FLOW_KEY_TYPE_L4_SRC, "l4-src-only") \
|
|
_ (62, FLOW_KEY_TYPE_L3_DST, "l3-dst-only") \
|
|
_ (63, FLOW_KEY_TYPE_L3_SRC, "l3-src-only")
|
|
|
|
typedef struct
|
|
{
|
|
u16 src_port;
|
|
u16 dst_port;
|
|
u32 verification_tag;
|
|
u32 cksum;
|
|
} sctp_header_t;
|
|
|
|
typedef struct
|
|
{
|
|
u8 ver_flags;
|
|
u8 type;
|
|
u16 length;
|
|
u32 teid;
|
|
} gtpu_header_t;
|
|
|
|
static void
|
|
oct_flow_convert_rss_types (u64 *key, u64 rss_types)
|
|
{
|
|
#define _(a, b, c) \
|
|
if (rss_types & (1UL << a)) \
|
|
*key |= b;
|
|
|
|
foreach_oct_flow_rss_types
|
|
#undef _
|
|
|
|
return;
|
|
}
|
|
|
|
vnet_dev_rv_t
|
|
oct_flow_validate_params (vlib_main_t *vm, vnet_dev_port_t *port,
|
|
vnet_dev_port_cfg_type_t type, u32 flow_index,
|
|
uword *priv_data)
|
|
{
|
|
vnet_flow_t *flow = vnet_get_flow (flow_index);
|
|
u32 last_queue;
|
|
u32 qid;
|
|
|
|
if (type == VNET_DEV_PORT_CFG_GET_RX_FLOW_COUNTER ||
|
|
type == VNET_DEV_PORT_CFG_RESET_RX_FLOW_COUNTER)
|
|
{
|
|
log_err (port->dev, "Unsupported request type");
|
|
return VNET_DEV_ERR_NOT_SUPPORTED;
|
|
}
|
|
|
|
if (OCT_FLOW_UNSUPPORTED_ACTIONS (flow))
|
|
{
|
|
log_err (port->dev, "Unsupported flow action");
|
|
return VNET_DEV_ERR_NOT_SUPPORTED;
|
|
}
|
|
|
|
if (flow->actions & VNET_FLOW_ACTION_REDIRECT_TO_QUEUE)
|
|
{
|
|
qid = flow->redirect_queue;
|
|
if (qid > port->intf.num_rx_queues - 1 || qid < 0)
|
|
{
|
|
log_err (port->dev,
|
|
"Given Q(%d) is invalid, supported range is %d-%d", qid, 0,
|
|
port->intf.num_rx_queues - 1);
|
|
return VNET_DEV_ERR_NOT_SUPPORTED;
|
|
}
|
|
}
|
|
|
|
if (flow->actions & VNET_FLOW_ACTION_RSS)
|
|
{
|
|
last_queue = flow->queue_index + flow->queue_num;
|
|
if (last_queue > port->intf.num_rx_queues - 1)
|
|
{
|
|
log_err (port->dev,
|
|
"Given Q range(%d-%d) is invalid, supported range is %d-%d",
|
|
flow->queue_index, flow->queue_index + flow->queue_num, 0,
|
|
port->intf.num_rx_queues - 1);
|
|
return VNET_DEV_ERR_NOT_SUPPORTED;
|
|
}
|
|
}
|
|
return VNET_DEV_OK;
|
|
}
|
|
|
|
static vnet_dev_rv_t
|
|
oct_flow_rule_create (vnet_dev_port_t *port, struct roc_npc_action *actions,
|
|
struct roc_npc_item_info *item_info, vnet_flow_t *flow,
|
|
uword *private_data)
|
|
{
|
|
oct_port_t *oct_port = vnet_dev_get_port_data (port);
|
|
struct roc_npc_attr attr = { .priority = 1, .ingress = 1 };
|
|
struct roc_npc_flow *npc_flow;
|
|
oct_flow_entry_t *flow_entry;
|
|
struct roc_npc *npc;
|
|
int rv = 0;
|
|
|
|
npc = &oct_port->npc;
|
|
|
|
npc_flow =
|
|
roc_npc_flow_create (npc, &attr, item_info, actions, npc->pf_func, &rv);
|
|
if (rv)
|
|
{
|
|
log_err (port->dev, "roc_npc_flow_create failed with '%s' error",
|
|
roc_error_msg_get (rv));
|
|
return VNET_DEV_ERR_NOT_SUPPORTED;
|
|
}
|
|
roc_npc_mcam_clear_counter (npc, npc_flow->ctr_id);
|
|
|
|
pool_get_zero (oct_port->flow_entries, flow_entry);
|
|
flow_entry->index = flow_entry - oct_port->flow_entries;
|
|
flow_entry->vnet_flow_index = flow->index;
|
|
flow_entry->npc_flow = npc_flow;
|
|
|
|
*private_data = flow_entry->index;
|
|
|
|
return VNET_DEV_OK;
|
|
}
|
|
|
|
static vnet_dev_rv_t
|
|
oct_flow_add (vlib_main_t *vm, vnet_dev_port_t *port, vnet_flow_t *flow,
|
|
uword *private_data)
|
|
{
|
|
struct roc_npc_item_info item_info[ROC_NPC_ITEM_TYPE_END] = {};
|
|
struct roc_npc_action actions[ROC_NPC_ITEM_TYPE_END] = {};
|
|
oct_port_t *oct_port = vnet_dev_get_port_data (port);
|
|
u16 l4_src_port = 0, l4_dst_port = 0;
|
|
u16 l4_src_mask = 0, l4_dst_mask = 0;
|
|
struct roc_npc_action_rss rss_conf = {};
|
|
struct roc_npc_action_queue conf = {};
|
|
struct roc_npc_action_mark mark = {};
|
|
struct roc_npc *npc = &oct_port->npc;
|
|
vnet_dev_rv_t rv = VNET_DEV_OK;
|
|
int layer = 0, index = 0;
|
|
u16 *queues = NULL;
|
|
u64 flow_key = 0;
|
|
u8 proto = 0;
|
|
u16 action = 0;
|
|
|
|
if (FLOW_IS_ETHERNET_CLASS (flow))
|
|
{
|
|
ethernet_header_t eth_spec = { .type = clib_host_to_net_u16 (
|
|
flow->ethernet.eth_hdr.type) },
|
|
eth_mask = { .type = 0xFFFF };
|
|
|
|
item_info[layer].spec = (void *) ð_spec;
|
|
item_info[layer].mask = (void *) ð_mask;
|
|
item_info[layer].size = sizeof (ethernet_header_t);
|
|
item_info[layer].type = ROC_NPC_ITEM_TYPE_ETH;
|
|
layer++;
|
|
}
|
|
|
|
else if (FLOW_IS_IPV4_CLASS (flow))
|
|
{
|
|
vnet_flow_ip4_t *ip4_hdr = &flow->ip4;
|
|
proto = ip4_hdr->protocol.prot;
|
|
ip4_header_t ip4_spec = { .src_address = ip4_hdr->src_addr.addr,
|
|
.dst_address = ip4_hdr->dst_addr.addr },
|
|
ip4_mask = { .src_address = ip4_hdr->src_addr.mask,
|
|
.dst_address = ip4_hdr->dst_addr.mask };
|
|
|
|
item_info[layer].spec = (void *) &ip4_spec;
|
|
item_info[layer].mask = (void *) &ip4_mask;
|
|
item_info[layer].size = sizeof (ip4_header_t);
|
|
item_info[layer].type = ROC_NPC_ITEM_TYPE_IPV4;
|
|
layer++;
|
|
|
|
if (FLOW_IS_L4_TYPE (flow))
|
|
{
|
|
vnet_flow_ip4_n_tuple_t *ip4_tuple_hdr = &flow->ip4_n_tuple;
|
|
|
|
l4_src_port = clib_host_to_net_u16 (ip4_tuple_hdr->src_port.port);
|
|
l4_dst_port = clib_host_to_net_u16 (ip4_tuple_hdr->dst_port.port);
|
|
l4_src_mask = clib_host_to_net_u16 (ip4_tuple_hdr->src_port.mask);
|
|
l4_dst_mask = clib_host_to_net_u16 (ip4_tuple_hdr->dst_port.mask);
|
|
}
|
|
}
|
|
else if (FLOW_IS_IPV6_CLASS (flow))
|
|
{
|
|
vnet_flow_ip6_t *ip6_hdr = &flow->ip6;
|
|
proto = ip6_hdr->protocol.prot;
|
|
ip6_header_t ip6_spec = { .src_address = ip6_hdr->src_addr.addr,
|
|
.dst_address = ip6_hdr->dst_addr.addr },
|
|
ip6_mask = { .src_address = ip6_hdr->src_addr.mask,
|
|
.dst_address = ip6_hdr->dst_addr.mask };
|
|
|
|
item_info[layer].spec = (void *) &ip6_spec;
|
|
item_info[layer].mask = (void *) &ip6_mask;
|
|
item_info[layer].size = sizeof (ip6_header_t);
|
|
item_info[layer].type = ROC_NPC_ITEM_TYPE_IPV6;
|
|
layer++;
|
|
|
|
if (FLOW_IS_L4_TYPE (flow))
|
|
{
|
|
vnet_flow_ip6_n_tuple_t *ip6_tuple_hdr = &flow->ip6_n_tuple;
|
|
|
|
l4_src_port = clib_host_to_net_u16 (ip6_tuple_hdr->src_port.port);
|
|
l4_dst_port = clib_host_to_net_u16 (ip6_tuple_hdr->dst_port.port);
|
|
l4_src_mask = clib_host_to_net_u16 (ip6_tuple_hdr->src_port.mask);
|
|
l4_dst_mask = clib_host_to_net_u16 (ip6_tuple_hdr->dst_port.mask);
|
|
}
|
|
}
|
|
|
|
if (!proto)
|
|
goto end_item_info;
|
|
|
|
switch (proto)
|
|
{
|
|
case IP_PROTOCOL_UDP:
|
|
item_info[layer].type = ROC_NPC_ITEM_TYPE_UDP;
|
|
|
|
udp_header_t udp_spec = { .src_port = l4_src_port,
|
|
.dst_port = l4_dst_port },
|
|
udp_mask = { .src_port = l4_src_mask,
|
|
.dst_port = l4_dst_mask };
|
|
|
|
item_info[layer].spec = (void *) &udp_spec;
|
|
item_info[layer].mask = (void *) &udp_mask;
|
|
item_info[layer].size = sizeof (udp_header_t);
|
|
layer++;
|
|
|
|
if (FLOW_IS_L4_TUNNEL_TYPE (flow))
|
|
{
|
|
switch (flow->type)
|
|
{
|
|
case VNET_FLOW_TYPE_IP4_GTPU:
|
|
item_info[layer].type = ROC_NPC_ITEM_TYPE_GTPU;
|
|
gtpu_header_t gtpu_spec = { .teid = clib_host_to_net_u32 (
|
|
flow->ip4_gtpu.teid) },
|
|
gtpu_mask = { .teid = 0XFFFFFFFF };
|
|
|
|
item_info[layer].spec = (void *) >pu_spec;
|
|
item_info[layer].mask = (void *) >pu_mask;
|
|
item_info[layer].size = sizeof (gtpu_header_t);
|
|
layer++;
|
|
break;
|
|
|
|
default:
|
|
log_err (port->dev, "Unsupported L4 tunnel type");
|
|
return VNET_DEV_ERR_NOT_SUPPORTED;
|
|
}
|
|
} /* FLOW_IS_L4_TUNNEL_TYPE */
|
|
break;
|
|
|
|
case IP_PROTOCOL_TCP:
|
|
item_info[layer].type = ROC_NPC_ITEM_TYPE_TCP;
|
|
|
|
tcp_header_t tcp_spec = { .src_port = l4_src_port,
|
|
.dst_port = l4_dst_port },
|
|
tcp_mask = { .src_port = l4_src_mask,
|
|
.dst_port = l4_dst_mask };
|
|
|
|
item_info[layer].spec = (void *) &tcp_spec;
|
|
item_info[layer].mask = (void *) &tcp_mask;
|
|
item_info[layer].size = sizeof (tcp_header_t);
|
|
layer++;
|
|
break;
|
|
|
|
case IP_PROTOCOL_SCTP:
|
|
item_info[layer].type = ROC_NPC_ITEM_TYPE_SCTP;
|
|
|
|
sctp_header_t sctp_spec = { .src_port = l4_src_port,
|
|
.dst_port = l4_dst_port },
|
|
sctp_mask = { .src_port = l4_src_mask,
|
|
.dst_port = l4_dst_mask };
|
|
|
|
item_info[layer].spec = (void *) &sctp_spec;
|
|
item_info[layer].mask = (void *) &sctp_mask;
|
|
item_info[layer].size = sizeof (sctp_header_t);
|
|
layer++;
|
|
break;
|
|
|
|
case IP_PROTOCOL_IPSEC_ESP:
|
|
item_info[layer].type = ROC_NPC_ITEM_TYPE_ESP;
|
|
esp_header_t esp_spec = { .spi = clib_host_to_net_u32 (
|
|
flow->ip4_ipsec_esp.spi) },
|
|
esp_mask = { .spi = 0xFFFFFFFF };
|
|
|
|
item_info[layer].spec = (void *) &esp_spec;
|
|
item_info[layer].mask = (void *) &esp_mask;
|
|
item_info[layer].size = sizeof (u32);
|
|
layer++;
|
|
break;
|
|
|
|
default:
|
|
log_err (port->dev, "Unsupported IP protocol '%U'", format_ip_protocol,
|
|
proto);
|
|
return VNET_DEV_ERR_NOT_SUPPORTED;
|
|
}
|
|
|
|
end_item_info:
|
|
item_info[layer].type = ROC_NPC_ITEM_TYPE_END;
|
|
|
|
if (flow->actions & VNET_FLOW_ACTION_REDIRECT_TO_QUEUE)
|
|
{
|
|
conf.index = flow->redirect_queue;
|
|
actions[action].type = ROC_NPC_ACTION_TYPE_QUEUE;
|
|
actions[action].conf = &conf;
|
|
action++;
|
|
}
|
|
|
|
else if (flow->actions & VNET_FLOW_ACTION_DROP)
|
|
{
|
|
actions[action].type = ROC_NPC_ACTION_TYPE_DROP;
|
|
action++;
|
|
}
|
|
|
|
else if (flow->actions & VNET_FLOW_ACTION_RSS)
|
|
{
|
|
if (!flow->queue_num)
|
|
{
|
|
log_err (port->dev, "RSS action has no queues");
|
|
return VNET_DEV_ERR_NOT_SUPPORTED;
|
|
}
|
|
queues = clib_mem_alloc (sizeof (u16) * port->intf.num_rx_queues);
|
|
|
|
for (index = 0; index < flow->queue_num; index++)
|
|
queues[index] = flow->queue_index++;
|
|
|
|
oct_flow_convert_rss_types (&flow_key, flow->rss_types);
|
|
if (!flow_key)
|
|
{
|
|
log_err (port->dev, "Invalid RSS hash function");
|
|
return VNET_DEV_ERR_NOT_SUPPORTED;
|
|
}
|
|
npc->flowkey_cfg_state = flow_key;
|
|
rss_conf.queue_num = flow->queue_num;
|
|
rss_conf.queue = queues;
|
|
|
|
actions[action].type = ROC_NPC_ACTION_TYPE_RSS;
|
|
actions[action].conf = &rss_conf;
|
|
action++;
|
|
}
|
|
|
|
if (flow->actions & VNET_FLOW_ACTION_MARK)
|
|
{
|
|
if (flow->mark_flow_id == 0 ||
|
|
flow->mark_flow_id > (NPC_FLOW_FLAG_VAL - 2))
|
|
{
|
|
log_err (port->dev, "mark flow id must be > 0 and < 0xfffe");
|
|
return VNET_DEV_ERR_NOT_SUPPORTED;
|
|
}
|
|
/* RoC library adds 1 to id, so subtract 1 */
|
|
mark.id = flow->mark_flow_id - 1;
|
|
actions[action].type = ROC_NPC_ACTION_TYPE_MARK;
|
|
actions[action].conf = &mark;
|
|
action++;
|
|
}
|
|
|
|
/* make count as default action */
|
|
actions[action].type = ROC_NPC_ACTION_TYPE_COUNT;
|
|
actions[action + 1].type = ROC_NPC_ACTION_TYPE_END;
|
|
|
|
rv = oct_flow_rule_create (port, actions, item_info, flow, private_data);
|
|
|
|
if (queues)
|
|
clib_mem_free (queues);
|
|
|
|
return rv;
|
|
}
|
|
|
|
static vnet_dev_rv_t
|
|
oct_flow_del (vlib_main_t *vm, vnet_dev_port_t *port, vnet_flow_t *flow,
|
|
uword *private_data)
|
|
{
|
|
oct_port_t *oct_port = vnet_dev_get_port_data (port);
|
|
struct roc_npc *npc = &oct_port->npc;
|
|
struct roc_npc_flow *npc_flow;
|
|
oct_flow_entry_t *flow_entry;
|
|
int rv = 0, index;
|
|
|
|
index = *private_data;
|
|
flow_entry = pool_elt_at_index (oct_port->flow_entries, index);
|
|
npc_flow = flow_entry->npc_flow;
|
|
rv = roc_npc_flow_destroy (npc, npc_flow);
|
|
if (rv)
|
|
{
|
|
log_err (port->dev, "roc_npc_flow_destroy failed with '%s' error",
|
|
roc_error_msg_get (rv));
|
|
return VNET_DEV_ERR_NOT_SUPPORTED;
|
|
}
|
|
pool_put (oct_port->flow_entries, flow_entry);
|
|
|
|
return VNET_DEV_OK;
|
|
}
|
|
|
|
vnet_dev_rv_t
|
|
oct_flow_query (vlib_main_t *vm, vnet_dev_port_t *port, u32 flow_index,
|
|
uword private_data, u64 *hits)
|
|
{
|
|
oct_port_t *oct_port = vnet_dev_get_port_data (port);
|
|
struct roc_npc *npc = &oct_port->npc;
|
|
struct roc_npc_flow *npc_flow;
|
|
oct_flow_entry_t *flow_entry;
|
|
i32 flow_count;
|
|
int rv = 0;
|
|
|
|
flow_count = pool_elts (oct_port->flow_entries);
|
|
if (!flow_count)
|
|
{
|
|
log_err (port->dev, "Flow entry pool is empty");
|
|
return VNET_DEV_ERR_NOT_SUPPORTED;
|
|
}
|
|
|
|
flow_entry = pool_elt_at_index (oct_port->flow_entries, private_data);
|
|
npc_flow = flow_entry->npc_flow;
|
|
if (npc_flow->ctr_id == NPC_COUNTER_NONE)
|
|
{
|
|
log_err (port->dev, "Counters are not available for given flow id (%u)",
|
|
flow_index);
|
|
return VNET_DEV_ERR_NOT_SUPPORTED;
|
|
}
|
|
|
|
rv = roc_npc_mcam_read_counter (npc, npc_flow->ctr_id, hits);
|
|
if (rv != 0)
|
|
{
|
|
log_err (port->dev, "Error reading flow counter for given flow id (%u)",
|
|
flow_index);
|
|
return VNET_DEV_ERR_INTERNAL;
|
|
}
|
|
|
|
return VNET_DEV_OK;
|
|
}
|
|
|
|
vnet_dev_rv_t
|
|
oct_flow_ops_fn (vlib_main_t *vm, vnet_dev_port_t *port,
|
|
vnet_dev_port_cfg_type_t type, u32 flow_index,
|
|
uword *priv_data)
|
|
{
|
|
vnet_flow_t *flow = vnet_get_flow (flow_index);
|
|
|
|
if (type == VNET_DEV_PORT_CFG_ADD_RX_FLOW)
|
|
return oct_flow_add (vm, port, flow, priv_data);
|
|
|
|
if (type == VNET_DEV_PORT_CFG_DEL_RX_FLOW)
|
|
return oct_flow_del (vm, port, flow, priv_data);
|
|
|
|
return VNET_DEV_ERR_NOT_SUPPORTED;
|
|
}
|