Commit 4d1bbdf5 authored by David S. Miller's avatar David S. Miller

Merge branch 'sparx5-sorted-VCAP-rules'

Steen Hegelund says:

====================
net: Add support for sorted VCAP rules in Sparx5

This provides support for adding Sparx5 VCAP rules in sorted order, VCAP
rule counters and TC filter matching on ARP frames.

It builds on top of the initial IS2 VCAP support found in these series:

https://lore.kernel.org/all/20221020130904.1215072-1-steen.hegelund@microchip.com/
https://lore.kernel.org/all/20221109114116.3612477-1-steen.hegelund@microchip.com/

Functionality
=============

When a new VCAP rule is added the driver will now ensure that the rule is
inserted in sorted order, and when a rule is removed, the remaining rules
will be moved to keep the sorted order and remove any gaps in the VCAP
address space.

A VCAP rule is ordered using these 3 values:

 - Rule size: the count of VCAP addresses used by the rule.  The largest
   rule have highest priority

 - Rule User: The rules are ordered by the user enumeration

 - Priority: The priority provided in the flower filter.  The lowest value
   has the highest priority.

A VCAP instance may contain the counter as part of the VCAP cache area, and
this counter may be one or more bits in width.  This type of counter
automatically increments its value when the rule is hit.

Other VCAP instances have a dedicated counter area outside of the VCAP and
in this case the rule must contain the counter id to be able to locate the
counter value and cause the counter to be incremented.  In this case there
must also be a VCAP rule action that sets the counter id.

The Sparx5 IS2 VCAP uses a dedicated counter area with 32bit counters.

This series adds support for getting VCAP rule counters and provide these
via the TC statistic interface.

This only support packet counters, not byte counters.

Finally the series adds support for the ARP frame dissector and configures
the Sparx5 IS2 VCAP to generate the ARP keyset when ARP traffic is
received.

Delivery:
=========

This is current plan for delivering the full VCAP feature set of Sparx5:

- DebugFS support for inspecting rules
- TC protocol all support
- Sparx5 IS0 VCAP support
- TC policer and drop action support (depends on the Sparx5 QoS support
  upstreamed separately)
- Sparx5 ES0 VCAP support
- TC flower template support
- TC matchall filter support for mirroring and policing ports
- TC flower filter mirror action support
- Sparx5 ES2 VCAP support
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 2fd450cd dccc30cc
......@@ -21,15 +21,34 @@ struct sparx5_tc_flower_parse_usage {
unsigned int used_keys;
};
struct sparx5_tc_rule_pkt_cnt {
u64 cookie;
u32 pkts;
};
/* These protocols have dedicated keysets in IS2 and a TC dissector
* ETH_P_ARP does not have a TC dissector
*/
static u16 sparx5_tc_known_etypes[] = {
ETH_P_ALL,
ETH_P_ARP,
ETH_P_IP,
ETH_P_IPV6,
};
enum sparx5_is2_arp_opcode {
SPX5_IS2_ARP_REQUEST,
SPX5_IS2_ARP_REPLY,
SPX5_IS2_RARP_REQUEST,
SPX5_IS2_RARP_REPLY,
};
enum tc_arp_opcode {
TC_ARP_OP_RESERVED,
TC_ARP_OP_REQUEST,
TC_ARP_OP_REPLY,
};
static bool sparx5_tc_is_known_etype(u16 etype)
{
int idx;
......@@ -404,6 +423,67 @@ sparx5_tc_flower_handler_tcp_usage(struct sparx5_tc_flower_parse_usage *st)
return err;
}
static int
sparx5_tc_flower_handler_arp_usage(struct sparx5_tc_flower_parse_usage *st)
{
struct flow_match_arp mt;
u16 value, mask;
u32 ipval, ipmsk;
int err;
flow_rule_match_arp(st->frule, &mt);
if (mt.mask->op) {
mask = 0x3;
if (st->l3_proto == ETH_P_ARP) {
value = mt.key->op == TC_ARP_OP_REQUEST ?
SPX5_IS2_ARP_REQUEST :
SPX5_IS2_ARP_REPLY;
} else { /* RARP */
value = mt.key->op == TC_ARP_OP_REQUEST ?
SPX5_IS2_RARP_REQUEST :
SPX5_IS2_RARP_REPLY;
}
err = vcap_rule_add_key_u32(st->vrule, VCAP_KF_ARP_OPCODE,
value, mask);
if (err)
goto out;
}
/* The IS2 ARP keyset does not support ARP hardware addresses */
if (!is_zero_ether_addr(mt.mask->sha) ||
!is_zero_ether_addr(mt.mask->tha))
goto out;
if (mt.mask->sip) {
ipval = be32_to_cpu((__force __be32)mt.key->sip);
ipmsk = be32_to_cpu((__force __be32)mt.mask->sip);
err = vcap_rule_add_key_u32(st->vrule, VCAP_KF_L3_IP4_SIP,
ipval, ipmsk);
if (err)
goto out;
}
if (mt.mask->tip) {
ipval = be32_to_cpu((__force __be32)mt.key->tip);
ipmsk = be32_to_cpu((__force __be32)mt.mask->tip);
err = vcap_rule_add_key_u32(st->vrule, VCAP_KF_L3_IP4_DIP,
ipval, ipmsk);
if (err)
goto out;
}
st->used_keys |= BIT(FLOW_DISSECTOR_KEY_ARP);
return err;
out:
NL_SET_ERR_MSG_MOD(st->fco->common.extack, "arp parse error");
return err;
}
static int
sparx5_tc_flower_handler_ip_usage(struct sparx5_tc_flower_parse_usage *st)
{
......@@ -438,6 +518,7 @@ static int (*sparx5_tc_flower_usage_handlers[])(struct sparx5_tc_flower_parse_us
[FLOW_DISSECTOR_KEY_BASIC] = sparx5_tc_flower_handler_basic_usage,
[FLOW_DISSECTOR_KEY_VLAN] = sparx5_tc_flower_handler_vlan_usage,
[FLOW_DISSECTOR_KEY_TCP] = sparx5_tc_flower_handler_tcp_usage,
[FLOW_DISSECTOR_KEY_ARP] = sparx5_tc_flower_handler_arp_usage,
[FLOW_DISSECTOR_KEY_IP] = sparx5_tc_flower_handler_ip_usage,
};
......@@ -529,6 +610,20 @@ static int sparx5_tc_flower_action_check(struct vcap_control *vctrl,
return 0;
}
/* Add a rule counter action - only IS2 is considered for now */
static int sparx5_tc_add_rule_counter(struct vcap_admin *admin,
struct vcap_rule *vrule)
{
int err;
err = vcap_rule_add_action_u32(vrule, VCAP_AF_CNT_ID, vrule->id);
if (err)
return err;
vcap_rule_set_counter_id(vrule, vrule->id);
return err;
}
static int sparx5_tc_flower_replace(struct net_device *ndev,
struct flow_cls_offload *fco,
struct vcap_admin *admin)
......@@ -554,6 +649,11 @@ static int sparx5_tc_flower_replace(struct net_device *ndev,
vrule->cookie = fco->cookie;
sparx5_tc_use_dissectors(fco, admin, vrule, &l3_proto);
err = sparx5_tc_add_rule_counter(admin, vrule);
if (err)
goto out;
frule = flow_cls_offload_flow_rule(fco);
flow_action_for_each(idx, act, &frule->action) {
switch (act->id) {
......@@ -632,6 +732,48 @@ static int sparx5_tc_flower_destroy(struct net_device *ndev,
return err;
}
/* Collect packet counts from all rules with the same cookie */
static int sparx5_tc_rule_counter_cb(void *arg, struct vcap_rule *rule)
{
struct sparx5_tc_rule_pkt_cnt *rinfo = arg;
struct vcap_counter counter;
int err = 0;
if (rule->cookie == rinfo->cookie) {
err = vcap_rule_get_counter(rule, &counter);
if (err)
return err;
rinfo->pkts += counter.value;
/* Reset the rule counter */
counter.value = 0;
vcap_rule_set_counter(rule, &counter);
}
return err;
}
static int sparx5_tc_flower_stats(struct net_device *ndev,
struct flow_cls_offload *fco,
struct vcap_admin *admin)
{
struct sparx5_port *port = netdev_priv(ndev);
struct sparx5_tc_rule_pkt_cnt rinfo = {};
struct vcap_control *vctrl;
ulong lastused = 0;
u64 drops = 0;
u32 pkts = 0;
int err;
rinfo.cookie = fco->cookie;
vctrl = port->sparx5->vcap_ctrl;
err = vcap_rule_iter(vctrl, sparx5_tc_rule_counter_cb, &rinfo);
if (err)
return err;
pkts = rinfo.pkts;
flow_stats_update(&fco->stats, 0x0, pkts, drops, lastused,
FLOW_ACTION_HW_STATS_IMMEDIATE);
return err;
}
int sparx5_tc_flower(struct net_device *ndev, struct flow_cls_offload *fco,
bool ingress)
{
......@@ -653,6 +795,8 @@ int sparx5_tc_flower(struct net_device *ndev, struct flow_cls_offload *fco,
return sparx5_tc_flower_replace(ndev, fco, admin);
case FLOW_CLS_DESTROY:
return sparx5_tc_flower_destroy(ndev, fco, admin);
case FLOW_CLS_STATS:
return sparx5_tc_flower_stats(ndev, fco, admin);
default:
return -EOPNOTSUPP;
}
......
......@@ -428,15 +428,58 @@ static void sparx5_vcap_cache_write(struct net_device *ndev,
default:
break;
}
if (sel & VCAP_SEL_COUNTER) {
start = start & 0xfff; /* counter limit */
if (admin->vinst == 0)
spx5_wr(admin->cache.counter, sparx5,
ANA_ACL_CNT_A(start));
else
spx5_wr(admin->cache.counter, sparx5,
ANA_ACL_CNT_B(start));
spx5_wr(admin->cache.sticky, sparx5,
VCAP_SUPER_VCAP_CNT_DAT(0));
}
}
/* API callback used for reading from the VCAP into the VCAP cache */
static void sparx5_vcap_cache_read(struct net_device *ndev,
struct vcap_admin *admin,
enum vcap_selection sel, u32 start,
enum vcap_selection sel,
u32 start,
u32 count)
{
/* this will be added later */
struct sparx5_port *port = netdev_priv(ndev);
struct sparx5 *sparx5 = port->sparx5;
u32 *keystr, *mskstr, *actstr;
int idx;
keystr = &admin->cache.keystream[start];
mskstr = &admin->cache.maskstream[start];
actstr = &admin->cache.actionstream[start];
if (sel & VCAP_SEL_ENTRY) {
for (idx = 0; idx < count; ++idx) {
keystr[idx] = spx5_rd(sparx5,
VCAP_SUPER_VCAP_ENTRY_DAT(idx));
mskstr[idx] = ~spx5_rd(sparx5,
VCAP_SUPER_VCAP_MASK_DAT(idx));
}
}
if (sel & VCAP_SEL_ACTION) {
for (idx = 0; idx < count; ++idx)
actstr[idx] = spx5_rd(sparx5,
VCAP_SUPER_VCAP_ACTION_DAT(idx));
}
if (sel & VCAP_SEL_COUNTER) {
start = start & 0xfff; /* counter limit */
if (admin->vinst == 0)
admin->cache.counter =
spx5_rd(sparx5, ANA_ACL_CNT_A(start));
else
admin->cache.counter =
spx5_rd(sparx5, ANA_ACL_CNT_B(start));
admin->cache.sticky =
spx5_rd(sparx5, VCAP_SUPER_VCAP_CNT_DAT(0));
}
}
/* API callback used for initializing a VCAP address range */
......@@ -477,7 +520,32 @@ static void sparx5_vcap_update(struct net_device *ndev,
static void sparx5_vcap_move(struct net_device *ndev, struct vcap_admin *admin,
u32 addr, int offset, int count)
{
/* this will be added later */
struct sparx5_port *port = netdev_priv(ndev);
struct sparx5 *sparx5 = port->sparx5;
enum vcap_command cmd;
u16 mv_num_pos;
u16 mv_size;
mv_size = count - 1;
if (offset > 0) {
mv_num_pos = offset - 1;
cmd = VCAP_CMD_MOVE_DOWN;
} else {
mv_num_pos = -offset - 1;
cmd = VCAP_CMD_MOVE_UP;
}
spx5_wr(VCAP_SUPER_CFG_MV_NUM_POS_SET(mv_num_pos) |
VCAP_SUPER_CFG_MV_SIZE_SET(mv_size),
sparx5, VCAP_SUPER_CFG);
spx5_wr(VCAP_SUPER_CTRL_UPDATE_CMD_SET(cmd) |
VCAP_SUPER_CTRL_UPDATE_ENTRY_DIS_SET(0) |
VCAP_SUPER_CTRL_UPDATE_ACTION_DIS_SET(0) |
VCAP_SUPER_CTRL_UPDATE_CNT_DIS_SET(0) |
VCAP_SUPER_CTRL_UPDATE_ADDR_SET(addr) |
VCAP_SUPER_CTRL_CLEAR_CACHE_SET(false) |
VCAP_SUPER_CTRL_UPDATE_SHOT_SET(true),
sparx5, VCAP_SUPER_CTRL);
sparx5_vcap_wait_super_update(sparx5);
}
/* Provide port information via a callback interface */
......@@ -540,7 +608,7 @@ static void sparx5_vcap_port_key_selection(struct sparx5 *sparx5,
VCAP_IS2_PS_IPV4_UC_IP4_TCP_UDP_OTHER,
VCAP_IS2_PS_IPV6_MC_IP_7TUPLE,
VCAP_IS2_PS_IPV6_UC_IP_7TUPLE,
VCAP_IS2_PS_ARP_MAC_ETYPE);
VCAP_IS2_PS_ARP_ARP);
for (lookup = 0; lookup < admin->lookups; ++lookup) {
for (portno = 0; portno < SPX5_PORTS; ++portno) {
spx5_wr(keysel, sparx5,
......
......@@ -25,6 +25,8 @@ struct vcap_rule_internal {
int actionset_sw_regs; /* registers in a subword in an actionset */
int size; /* the size of the rule: max(entry, action) */
u32 addr; /* address in the VCAP at insertion */
u32 counter_id; /* counter id (if a dedicated counter is available) */
struct vcap_counter counter; /* last read counter value */
};
/* Moving a rule in the VCAP address space */
......@@ -651,6 +653,20 @@ static int vcap_write_rule(struct vcap_rule_internal *ri)
return 0;
}
static int vcap_write_counter(struct vcap_rule_internal *ri,
struct vcap_counter *ctr)
{
struct vcap_admin *admin = ri->admin;
admin->cache.counter = ctr->value;
admin->cache.sticky = ctr->sticky;
ri->vctrl->ops->cache_write(ri->ndev, admin, VCAP_SEL_COUNTER,
ri->counter_id, 0);
ri->vctrl->ops->update(ri->ndev, admin, VCAP_CMD_WRITE,
VCAP_SEL_COUNTER, ri->addr);
return 0;
}
/* Convert a chain id to a VCAP lookup index */
int vcap_chain_id_to_lookup(struct vcap_admin *admin, int cur_cid)
{
......@@ -941,6 +957,16 @@ int vcap_val_rule(struct vcap_rule *rule, u16 l3_proto)
}
EXPORT_SYMBOL_GPL(vcap_val_rule);
/* Entries are sorted with increasing values of sort_key.
* I.e. Lowest numerical sort_key is first in list.
* In order to locate largest keys first in list we negate the key size with
* (max_size - size).
*/
static u32 vcap_sort_key(u32 max_size, u32 size, u8 user, u16 prio)
{
return ((max_size - size) << 24) | (user << 16) | prio;
}
/* calculate the address of the next rule after this (lower address and prio) */
static u32 vcap_next_rule_addr(u32 addr, struct vcap_rule_internal *ri)
{
......@@ -970,17 +996,67 @@ static u32 vcap_set_rule_id(struct vcap_rule_internal *ri)
static int vcap_insert_rule(struct vcap_rule_internal *ri,
struct vcap_rule_move *move)
{
int sw_count = ri->vctrl->vcaps[ri->admin->vtype].sw_count;
struct vcap_rule_internal *duprule, *iter, *elem = NULL;
struct vcap_admin *admin = ri->admin;
struct vcap_rule_internal *duprule;
u32 addr;
ri->sort_key = vcap_sort_key(sw_count, ri->size, ri->data.user,
ri->data.priority);
/* Insert the new rule in the list of rule based on the sort key
* If the rule needs to be inserted between existing rules then move
* these rules to make room for the new rule and update their start
* address.
*/
list_for_each_entry(iter, &admin->rules, list) {
if (ri->sort_key < iter->sort_key) {
elem = iter;
break;
}
}
if (!elem) {
ri->addr = vcap_next_rule_addr(admin->last_used_addr, ri);
admin->last_used_addr = ri->addr;
/* Add a shallow copy of the rule to the VCAP list */
duprule = vcap_dup_rule(ri);
if (IS_ERR(duprule))
return PTR_ERR(duprule);
list_add_tail(&duprule->list, &admin->rules);
return 0;
}
/* Reuse the space of the current rule */
addr = elem->addr + elem->size;
ri->addr = vcap_next_rule_addr(addr, ri);
addr = ri->addr;
/* Only support appending rules for now */
ri->addr = vcap_next_rule_addr(admin->last_used_addr, ri);
admin->last_used_addr = ri->addr;
/* Add a shallow copy of the rule to the VCAP list */
duprule = vcap_dup_rule(ri);
if (IS_ERR(duprule))
return PTR_ERR(duprule);
list_add_tail(&duprule->list, &admin->rules);
/* Add before the current entry */
list_add_tail(&duprule->list, &elem->list);
/* Update the current rule */
elem->addr = vcap_next_rule_addr(addr, elem);
addr = elem->addr;
/* Update the address in the remaining rules in the list */
list_for_each_entry_continue(elem, &admin->rules, list) {
elem->addr = vcap_next_rule_addr(addr, elem);
addr = elem->addr;
}
/* Update the move info */
move->addr = admin->last_used_addr;
move->count = ri->addr - addr;
move->offset = admin->last_used_addr - addr;
admin->last_used_addr = addr;
return 0;
}
......@@ -1032,8 +1108,11 @@ struct vcap_rule *vcap_alloc_rule(struct vcap_control *vctrl,
{
struct vcap_rule_internal *ri;
struct vcap_admin *admin;
int maxsize;
int err, maxsize;
err = vcap_api_check(vctrl);
if (err)
return ERR_PTR(err);
if (!ndev)
return ERR_PTR(-ENODEV);
/* Get the VCAP instance */
......@@ -1098,12 +1177,57 @@ void vcap_free_rule(struct vcap_rule *rule)
}
EXPORT_SYMBOL_GPL(vcap_free_rule);
/* Return the alignment offset for a new rule address */
static int vcap_valid_rule_move(struct vcap_rule_internal *el, int offset)
{
return (el->addr + offset) % el->size;
}
/* Update the rule address with an offset */
static void vcap_adjust_rule_addr(struct vcap_rule_internal *el, int offset)
{
el->addr += offset;
}
/* Rules needs to be moved to fill the gap of the deleted rule */
static int vcap_fill_rule_gap(struct vcap_rule_internal *ri)
{
struct vcap_admin *admin = ri->admin;
struct vcap_rule_internal *elem;
struct vcap_rule_move move;
int gap = 0, offset = 0;
/* If the first rule is deleted: Move other rules to the top */
if (list_is_first(&ri->list, &admin->rules))
offset = admin->last_valid_addr + 1 - ri->addr - ri->size;
/* Locate gaps between odd size rules and adjust the move */
elem = ri;
list_for_each_entry_continue(elem, &admin->rules, list)
gap += vcap_valid_rule_move(elem, ri->size);
/* Update the address in the remaining rules in the list */
elem = ri;
list_for_each_entry_continue(elem, &admin->rules, list)
vcap_adjust_rule_addr(elem, ri->size + gap + offset);
/* Update the move info */
move.addr = admin->last_used_addr;
move.count = ri->addr - admin->last_used_addr - gap;
move.offset = -(ri->size + gap + offset);
/* Do the actual move operation */
vcap_move_rules(ri, &move);
return gap + offset;
}
/* Delete rule in a VCAP instance */
int vcap_del_rule(struct vcap_control *vctrl, struct net_device *ndev, u32 id)
{
struct vcap_rule_internal *ri, *elem;
struct vcap_admin *admin;
int err;
int gap = 0, err;
/* This will later also handle rule moving */
if (!ndev)
......@@ -1116,18 +1240,23 @@ int vcap_del_rule(struct vcap_control *vctrl, struct net_device *ndev, u32 id)
if (!ri)
return -EINVAL;
admin = ri->admin;
if (ri->addr > admin->last_used_addr)
gap = vcap_fill_rule_gap(ri);
/* Delete the rule from the list of rules and the cache */
list_del(&ri->list);
vctrl->ops->init(ndev, admin, admin->last_used_addr, ri->size + gap);
kfree(ri);
/* delete the rule in the cache */
vctrl->ops->init(ndev, admin, ri->addr, ri->size);
/* Update the last used address */
if (list_empty(&admin->rules)) {
admin->last_used_addr = admin->last_valid_addr;
} else {
/* update the address range end marker from the last rule in the list */
elem = list_last_entry(&admin->rules, struct vcap_rule_internal, list);
elem = list_last_entry(&admin->rules, struct vcap_rule_internal,
list);
admin->last_used_addr = elem->addr;
}
kfree(ri);
return 0;
}
EXPORT_SYMBOL_GPL(vcap_del_rule);
......@@ -1434,6 +1563,20 @@ int vcap_rule_add_action_u32(struct vcap_rule *rule,
}
EXPORT_SYMBOL_GPL(vcap_rule_add_action_u32);
static int vcap_read_counter(struct vcap_rule_internal *ri,
struct vcap_counter *ctr)
{
struct vcap_admin *admin = ri->admin;
ri->vctrl->ops->update(ri->ndev, admin, VCAP_CMD_READ, VCAP_SEL_COUNTER,
ri->addr);
ri->vctrl->ops->cache_read(ri->ndev, admin, VCAP_SEL_COUNTER,
ri->counter_id, 0);
ctr->value = admin->cache.counter;
ctr->sticky = admin->cache.sticky;
return 0;
}
/* Copy to host byte order */
void vcap_netbytes_copy(u8 *dst, u8 *src, int count)
{
......@@ -1577,6 +1720,72 @@ int vcap_enable_lookups(struct vcap_control *vctrl, struct net_device *ndev,
}
EXPORT_SYMBOL_GPL(vcap_enable_lookups);
/* Set a rule counter id (for certain vcaps only) */
void vcap_rule_set_counter_id(struct vcap_rule *rule, u32 counter_id)
{
struct vcap_rule_internal *ri = to_intrule(rule);
ri->counter_id = counter_id;
}
EXPORT_SYMBOL_GPL(vcap_rule_set_counter_id);
/* Provide all rules via a callback interface */
int vcap_rule_iter(struct vcap_control *vctrl,
int (*callback)(void *, struct vcap_rule *), void *arg)
{
struct vcap_rule_internal *ri;
struct vcap_admin *admin;
int ret;
ret = vcap_api_check(vctrl);
if (ret)
return ret;
/* Iterate all rules in each VCAP instance */
list_for_each_entry(admin, &vctrl->list, list) {
list_for_each_entry(ri, &admin->rules, list) {
ret = callback(arg, &ri->data);
if (ret)
return ret;
}
}
return 0;
}
EXPORT_SYMBOL_GPL(vcap_rule_iter);
int vcap_rule_set_counter(struct vcap_rule *rule, struct vcap_counter *ctr)
{
struct vcap_rule_internal *ri = to_intrule(rule);
int err;
err = vcap_api_check(ri->vctrl);
if (err)
return err;
if (!ctr) {
pr_err("%s:%d: counter is missing\n", __func__, __LINE__);
return -EINVAL;
}
return vcap_write_counter(ri, ctr);
}
EXPORT_SYMBOL_GPL(vcap_rule_set_counter);
int vcap_rule_get_counter(struct vcap_rule *rule, struct vcap_counter *ctr)
{
struct vcap_rule_internal *ri = to_intrule(rule);
int err;
err = vcap_api_check(ri->vctrl);
if (err)
return err;
if (!ctr) {
pr_err("%s:%d: counter is missing\n", __func__, __LINE__);
return -EINVAL;
}
return vcap_read_counter(ri, ctr);
}
EXPORT_SYMBOL_GPL(vcap_rule_get_counter);
#ifdef CONFIG_VCAP_KUNIT_TEST
#include "vcap_api_kunit.c"
#endif
......@@ -143,6 +143,11 @@ enum vcap_bit {
VCAP_BIT_1
};
struct vcap_counter {
u32 value;
bool sticky;
};
/* Enable/Disable the VCAP instance lookups. Chain id 0 means disable */
int vcap_enable_lookups(struct vcap_control *vctrl, struct net_device *ndev,
int chain_id, unsigned long cookie, bool enable);
......@@ -170,6 +175,8 @@ int vcap_set_rule_set_keyset(struct vcap_rule *rule,
/* Update the actionset for the rule */
int vcap_set_rule_set_actionset(struct vcap_rule *rule,
enum vcap_actionfield_set actionset);
/* Set a rule counter id (for certain VCAPs only) */
void vcap_rule_set_counter_id(struct vcap_rule *rule, u32 counter_id);
/* VCAP rule field operations */
int vcap_rule_add_key_bit(struct vcap_rule *rule, enum vcap_key_field key,
......@@ -187,6 +194,10 @@ int vcap_rule_add_action_bit(struct vcap_rule *rule,
int vcap_rule_add_action_u32(struct vcap_rule *rule,
enum vcap_action_field action, u32 value);
/* VCAP rule counter operations */
int vcap_rule_set_counter(struct vcap_rule *rule, struct vcap_counter *ctr);
int vcap_rule_get_counter(struct vcap_rule *rule, struct vcap_counter *ctr);
/* VCAP lookup operations */
/* Convert a chain id to a VCAP lookup index */
int vcap_chain_id_to_lookup(struct vcap_admin *admin, int cur_cid);
......@@ -199,6 +210,9 @@ const struct vcap_field *vcap_lookup_keyfield(struct vcap_rule *rule,
int vcap_lookup_rule_by_cookie(struct vcap_control *vctrl, u64 cookie);
/* Is the next chain id in the following lookup, possible in another VCAP */
bool vcap_is_next_lookup(struct vcap_control *vctrl, int cur_cid, int next_cid);
/* Provide all rules via a callback interface */
int vcap_rule_iter(struct vcap_control *vctrl,
int (*callback)(void *, struct vcap_rule *), void *arg);
/* Copy to host byte order */
void vcap_netbytes_copy(u8 *dst, u8 *src, int count);
......
......@@ -23,6 +23,9 @@ static u32 test_init_count;
static u32 test_hw_counter_id;
static struct vcap_cache_data test_hw_cache;
static struct net_device test_netdev = {};
static int test_move_addr;
static int test_move_offset;
static int test_move_count;
/* Callback used by the VCAP API */
static enum vcap_keyfield_set test_val_keyset(struct net_device *ndev,
......@@ -195,6 +198,9 @@ static void test_cache_update(struct net_device *ndev, struct vcap_admin *admin,
static void test_cache_move(struct net_device *ndev, struct vcap_admin *admin,
u32 addr, int offset, int count)
{
test_move_addr = addr;
test_move_offset = offset;
test_move_count = count;
}
/* Provide port information via a callback interface */
......@@ -242,6 +248,88 @@ static void vcap_test_api_init(struct vcap_admin *admin)
test_updateaddridx = 0;
}
/* Helper function to create a rule of a specific size */
static struct vcap_rule *
test_vcap_xn_rule_creator(struct kunit *test, int cid, enum vcap_user user,
u16 priority,
int id, int size, int expected_addr)
{
struct vcap_rule *rule = 0;
struct vcap_rule_internal *ri = 0;
enum vcap_keyfield_set keyset = VCAP_KFS_NO_VALUE;
enum vcap_actionfield_set actionset = VCAP_AFS_NO_VALUE;
int ret;
/* init before testing */
memset(test_updateaddr, 0, sizeof(test_updateaddr));
test_updateaddridx = 0;
test_move_addr = 0;
test_move_offset = 0;
test_move_count = 0;
switch (size) {
case 2:
keyset = VCAP_KFS_ETAG;
actionset = VCAP_AFS_CLASS_REDUCED;
break;
case 3:
keyset = VCAP_KFS_PURE_5TUPLE_IP4;
actionset = VCAP_AFS_CLASSIFICATION;
break;
case 6:
keyset = VCAP_KFS_NORMAL_5TUPLE_IP4;
actionset = VCAP_AFS_CLASSIFICATION;
break;
case 12:
keyset = VCAP_KFS_NORMAL_7TUPLE;
actionset = VCAP_AFS_FULL;
break;
default:
break;
}
/* Check that a valid size was used */
KUNIT_ASSERT_NE(test, VCAP_KFS_NO_VALUE, keyset);
/* Allocate the rule */
rule = vcap_alloc_rule(&test_vctrl, &test_netdev, cid, user, priority,
id);
KUNIT_EXPECT_PTR_NE(test, NULL, rule);
ri = (struct vcap_rule_internal *)rule;
/* Override rule keyset */
ret = vcap_set_rule_set_keyset(rule, keyset);
/* Add rule actions : there must be at least one action */
ret = vcap_rule_add_action_u32(rule, VCAP_AF_COSID_VAL, 0);
/* Override rule actionset */
ret = vcap_set_rule_set_actionset(rule, actionset);
ret = vcap_val_rule(rule, ETH_P_ALL);
KUNIT_EXPECT_EQ(test, 0, ret);
KUNIT_EXPECT_EQ(test, keyset, rule->keyset);
KUNIT_EXPECT_EQ(test, actionset, rule->actionset);
KUNIT_EXPECT_EQ(test, size, ri->size);
/* Add rule with write callback */
ret = vcap_add_rule(rule);
KUNIT_EXPECT_EQ(test, 0, ret);
KUNIT_EXPECT_EQ(test, expected_addr, ri->addr);
return rule;
}
/* Prepare testing rule deletion */
static void test_init_rule_deletion(void)
{
test_move_addr = 0;
test_move_offset = 0;
test_move_count = 0;
test_init_start = 0;
test_init_count = 0;
}
/* Define the test cases. */
static void vcap_api_set_bit_1_test(struct kunit *test)
......@@ -1333,6 +1421,414 @@ static void vcap_api_encode_rule_test(struct kunit *test)
KUNIT_EXPECT_EQ(test, true, ret);
}
static void vcap_api_set_rule_counter_test(struct kunit *test)
{
struct vcap_admin is2_admin = {
.cache = {
.counter = 100,
.sticky = true,
},
};
struct vcap_rule_internal ri = {
.data = {
.id = 1001,
},
.addr = 600,
.admin = &is2_admin,
.counter_id = 1002,
.vctrl = &test_vctrl,
};
struct vcap_rule_internal ri2 = {
.data = {
.id = 2001,
},
.addr = 700,
.admin = &is2_admin,
.counter_id = 2002,
.vctrl = &test_vctrl,
};
struct vcap_counter ctr = { .value = 0, .sticky = false};
struct vcap_counter ctr2 = { .value = 101, .sticky = true};
int ret;
vcap_test_api_init(&is2_admin);
list_add_tail(&ri.list, &is2_admin.rules);
list_add_tail(&ri2.list, &is2_admin.rules);
pr_info("%s:%d\n", __func__, __LINE__);
ret = vcap_rule_set_counter(&ri.data, &ctr);
pr_info("%s:%d\n", __func__, __LINE__);
KUNIT_EXPECT_EQ(test, 0, ret);
KUNIT_EXPECT_EQ(test, 1002, test_hw_counter_id);
KUNIT_EXPECT_EQ(test, 0, test_hw_cache.counter);
KUNIT_EXPECT_EQ(test, false, test_hw_cache.sticky);
KUNIT_EXPECT_EQ(test, 600, test_updateaddr[0]);
ret = vcap_rule_set_counter(&ri2.data, &ctr2);
KUNIT_EXPECT_EQ(test, 0, ret);
KUNIT_EXPECT_EQ(test, 2002, test_hw_counter_id);
KUNIT_EXPECT_EQ(test, 101, test_hw_cache.counter);
KUNIT_EXPECT_EQ(test, true, test_hw_cache.sticky);
KUNIT_EXPECT_EQ(test, 700, test_updateaddr[1]);
}
static void vcap_api_get_rule_counter_test(struct kunit *test)
{
struct vcap_admin is2_admin = {
.cache = {
.counter = 100,
.sticky = true,
},
};
struct vcap_rule_internal ri = {
.data = {
.id = 1010,
},
.addr = 400,
.admin = &is2_admin,
.counter_id = 1011,
.vctrl = &test_vctrl,
};
struct vcap_rule_internal ri2 = {
.data = {
.id = 2011,
},
.addr = 300,
.admin = &is2_admin,
.counter_id = 2012,
.vctrl = &test_vctrl,
};
struct vcap_counter ctr = {};
struct vcap_counter ctr2 = {};
int ret;
vcap_test_api_init(&is2_admin);
test_hw_cache.counter = 55;
test_hw_cache.sticky = true;
list_add_tail(&ri.list, &is2_admin.rules);
list_add_tail(&ri2.list, &is2_admin.rules);
ret = vcap_rule_get_counter(&ri.data, &ctr);
KUNIT_EXPECT_EQ(test, 0, ret);
KUNIT_EXPECT_EQ(test, 1011, test_hw_counter_id);
KUNIT_EXPECT_EQ(test, 55, ctr.value);
KUNIT_EXPECT_EQ(test, true, ctr.sticky);
KUNIT_EXPECT_EQ(test, 400, test_updateaddr[0]);
test_hw_cache.counter = 22;
test_hw_cache.sticky = false;
ret = vcap_rule_get_counter(&ri2.data, &ctr2);
KUNIT_EXPECT_EQ(test, 0, ret);
KUNIT_EXPECT_EQ(test, 2012, test_hw_counter_id);
KUNIT_EXPECT_EQ(test, 22, ctr2.value);
KUNIT_EXPECT_EQ(test, false, ctr2.sticky);
KUNIT_EXPECT_EQ(test, 300, test_updateaddr[1]);
}
static void vcap_api_rule_insert_in_order_test(struct kunit *test)
{
/* Data used by VCAP Library callback */
static u32 keydata[32] = {};
static u32 mskdata[32] = {};
static u32 actdata[32] = {};
struct vcap_admin admin = {
.vtype = VCAP_TYPE_IS0,
.first_cid = 10000,
.last_cid = 19999,
.lookups = 4,
.last_valid_addr = 3071,
.first_valid_addr = 0,
.last_used_addr = 800,
.cache = {
.keystream = keydata,
.maskstream = mskdata,
.actionstream = actdata,
},
};
vcap_test_api_init(&admin);
/* Create rules with different sizes and check that they are placed
* at the correct address in the VCAP according to size
*/
test_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 10, 500, 12, 780);
test_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 20, 400, 6, 774);
test_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 30, 300, 3, 771);
test_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 40, 200, 2, 768);
}
static void vcap_api_rule_insert_reverse_order_test(struct kunit *test)
{
/* Data used by VCAP Library callback */
static u32 keydata[32] = {};
static u32 mskdata[32] = {};
static u32 actdata[32] = {};
struct vcap_admin admin = {
.vtype = VCAP_TYPE_IS0,
.first_cid = 10000,
.last_cid = 19999,
.lookups = 4,
.last_valid_addr = 3071,
.first_valid_addr = 0,
.last_used_addr = 800,
.cache = {
.keystream = keydata,
.maskstream = mskdata,
.actionstream = actdata,
},
};
struct vcap_rule_internal *elem;
u32 exp_addr[] = {780, 774, 771, 768, 767};
int idx;
vcap_test_api_init(&admin);
/* Create rules with different sizes and check that they are placed
* at the correct address in the VCAP according to size
*/
test_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 20, 200, 2, 798);
KUNIT_EXPECT_EQ(test, 0, test_move_offset);
KUNIT_EXPECT_EQ(test, 0, test_move_count);
KUNIT_EXPECT_EQ(test, 0, test_move_addr);
test_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 30, 300, 3, 795);
KUNIT_EXPECT_EQ(test, 6, test_move_offset);
KUNIT_EXPECT_EQ(test, 3, test_move_count);
KUNIT_EXPECT_EQ(test, 798, test_move_addr);
test_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 40, 400, 6, 792);
KUNIT_EXPECT_EQ(test, 6, test_move_offset);
KUNIT_EXPECT_EQ(test, 6, test_move_count);
KUNIT_EXPECT_EQ(test, 792, test_move_addr);
test_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 50, 500, 12, 780);
KUNIT_EXPECT_EQ(test, 18, test_move_offset);
KUNIT_EXPECT_EQ(test, 12, test_move_count);
KUNIT_EXPECT_EQ(test, 786, test_move_addr);
idx = 0;
list_for_each_entry(elem, &admin.rules, list) {
KUNIT_EXPECT_EQ(test, exp_addr[idx], elem->addr);
++idx;
}
KUNIT_EXPECT_EQ(test, 768, admin.last_used_addr);
}
static void vcap_api_rule_remove_at_end_test(struct kunit *test)
{
/* Data used by VCAP Library callback */
static u32 keydata[32] = {};
static u32 mskdata[32] = {};
static u32 actdata[32] = {};
struct vcap_admin admin = {
.vtype = VCAP_TYPE_IS0,
.first_cid = 10000,
.last_cid = 19999,
.lookups = 4,
.last_valid_addr = 3071,
.first_valid_addr = 0,
.last_used_addr = 800,
.cache = {
.keystream = keydata,
.maskstream = mskdata,
.actionstream = actdata,
},
};
int ret;
vcap_test_api_init(&admin);
test_init_rule_deletion();
/* Create rules with different sizes and check that they are placed
* at the correct address in the VCAP according to size
*/
test_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 10, 500, 12, 780);
test_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 20, 400, 6, 774);
test_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 30, 300, 3, 771);
test_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 40, 200, 2, 768);
/* Remove rules again from the end */
ret = vcap_del_rule(&test_vctrl, &test_netdev, 200);
KUNIT_EXPECT_EQ(test, 0, ret);
KUNIT_EXPECT_EQ(test, 0, test_move_addr);
KUNIT_EXPECT_EQ(test, 0, test_move_offset);
KUNIT_EXPECT_EQ(test, 0, test_move_count);
KUNIT_EXPECT_EQ(test, 768, test_init_start);
KUNIT_EXPECT_EQ(test, 2, test_init_count);
KUNIT_EXPECT_EQ(test, 771, admin.last_used_addr);
ret = vcap_del_rule(&test_vctrl, &test_netdev, 300);
KUNIT_EXPECT_EQ(test, ret, 0);
KUNIT_EXPECT_EQ(test, 0, test_move_addr);
KUNIT_EXPECT_EQ(test, 0, test_move_offset);
KUNIT_EXPECT_EQ(test, 0, test_move_count);
KUNIT_EXPECT_EQ(test, 771, test_init_start);
KUNIT_EXPECT_EQ(test, 3, test_init_count);
KUNIT_EXPECT_EQ(test, 774, admin.last_used_addr);
ret = vcap_del_rule(&test_vctrl, &test_netdev, 400);
KUNIT_EXPECT_EQ(test, ret, 0);
KUNIT_EXPECT_EQ(test, 0, test_move_addr);
KUNIT_EXPECT_EQ(test, 0, test_move_offset);
KUNIT_EXPECT_EQ(test, 0, test_move_count);
KUNIT_EXPECT_EQ(test, 774, test_init_start);
KUNIT_EXPECT_EQ(test, 6, test_init_count);
KUNIT_EXPECT_EQ(test, 780, admin.last_used_addr);
ret = vcap_del_rule(&test_vctrl, &test_netdev, 500);
KUNIT_EXPECT_EQ(test, ret, 0);
KUNIT_EXPECT_EQ(test, 0, test_move_addr);
KUNIT_EXPECT_EQ(test, 0, test_move_offset);
KUNIT_EXPECT_EQ(test, 0, test_move_count);
KUNIT_EXPECT_EQ(test, 780, test_init_start);
KUNIT_EXPECT_EQ(test, 12, test_init_count);
KUNIT_EXPECT_EQ(test, 3071, admin.last_used_addr);
}
static void vcap_api_rule_remove_in_middle_test(struct kunit *test)
{
/* Data used by VCAP Library callback */
static u32 keydata[32] = {};
static u32 mskdata[32] = {};
static u32 actdata[32] = {};
struct vcap_admin admin = {
.vtype = VCAP_TYPE_IS0,
.first_cid = 10000,
.last_cid = 19999,
.lookups = 4,
.first_valid_addr = 0,
.last_used_addr = 800,
.last_valid_addr = 800 - 1,
.cache = {
.keystream = keydata,
.maskstream = mskdata,
.actionstream = actdata,
},
};
int ret;
vcap_test_api_init(&admin);
/* Create rules with different sizes and check that they are placed
* at the correct address in the VCAP according to size
*/
test_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 10, 500, 12, 780);
test_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 20, 400, 6, 774);
test_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 30, 300, 3, 771);
test_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 40, 200, 2, 768);
/* Remove rules in the middle */
test_init_rule_deletion();
ret = vcap_del_rule(&test_vctrl, &test_netdev, 400);
KUNIT_EXPECT_EQ(test, 0, ret);
KUNIT_EXPECT_EQ(test, 768, test_move_addr);
KUNIT_EXPECT_EQ(test, -6, test_move_offset);
KUNIT_EXPECT_EQ(test, 6, test_move_count);
KUNIT_EXPECT_EQ(test, 768, test_init_start);
KUNIT_EXPECT_EQ(test, 6, test_init_count);
KUNIT_EXPECT_EQ(test, 774, admin.last_used_addr);
test_init_rule_deletion();
ret = vcap_del_rule(&test_vctrl, &test_netdev, 300);
KUNIT_EXPECT_EQ(test, 0, ret);
KUNIT_EXPECT_EQ(test, 774, test_move_addr);
KUNIT_EXPECT_EQ(test, -4, test_move_offset);
KUNIT_EXPECT_EQ(test, 2, test_move_count);
KUNIT_EXPECT_EQ(test, 774, test_init_start);
KUNIT_EXPECT_EQ(test, 4, test_init_count);
KUNIT_EXPECT_EQ(test, 778, admin.last_used_addr);
test_init_rule_deletion();
ret = vcap_del_rule(&test_vctrl, &test_netdev, 500);
KUNIT_EXPECT_EQ(test, 0, ret);
KUNIT_EXPECT_EQ(test, 778, test_move_addr);
KUNIT_EXPECT_EQ(test, -20, test_move_offset);
KUNIT_EXPECT_EQ(test, 2, test_move_count);
KUNIT_EXPECT_EQ(test, 778, test_init_start);
KUNIT_EXPECT_EQ(test, 20, test_init_count);
KUNIT_EXPECT_EQ(test, 798, admin.last_used_addr);
test_init_rule_deletion();
ret = vcap_del_rule(&test_vctrl, &test_netdev, 200);
KUNIT_EXPECT_EQ(test, 0, ret);
KUNIT_EXPECT_EQ(test, 0, test_move_addr);
KUNIT_EXPECT_EQ(test, 0, test_move_offset);
KUNIT_EXPECT_EQ(test, 0, test_move_count);
KUNIT_EXPECT_EQ(test, 798, test_init_start);
KUNIT_EXPECT_EQ(test, 2, test_init_count);
KUNIT_EXPECT_EQ(test, 799, admin.last_used_addr);
}
static void vcap_api_rule_remove_in_front_test(struct kunit *test)
{
/* Data used by VCAP Library callback */
static u32 keydata[32] = {};
static u32 mskdata[32] = {};
static u32 actdata[32] = {};
struct vcap_admin admin = {
.vtype = VCAP_TYPE_IS0,
.first_cid = 10000,
.last_cid = 19999,
.lookups = 4,
.first_valid_addr = 0,
.last_used_addr = 800,
.last_valid_addr = 800 - 1,
.cache = {
.keystream = keydata,
.maskstream = mskdata,
.actionstream = actdata,
},
};
int ret;
vcap_test_api_init(&admin);
test_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 10, 500, 12, 780);
KUNIT_EXPECT_EQ(test, 780, admin.last_used_addr);
test_init_rule_deletion();
ret = vcap_del_rule(&test_vctrl, &test_netdev, 500);
KUNIT_EXPECT_EQ(test, 0, ret);
KUNIT_EXPECT_EQ(test, 0, test_move_addr);
KUNIT_EXPECT_EQ(test, 0, test_move_offset);
KUNIT_EXPECT_EQ(test, 0, test_move_count);
KUNIT_EXPECT_EQ(test, 780, test_init_start);
KUNIT_EXPECT_EQ(test, 12, test_init_count);
KUNIT_EXPECT_EQ(test, 799, admin.last_used_addr);
test_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 20, 400, 6, 792);
test_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 30, 300, 3, 789);
test_vcap_xn_rule_creator(test, 10000, VCAP_USER_QOS, 40, 200, 2, 786);
test_init_rule_deletion();
ret = vcap_del_rule(&test_vctrl, &test_netdev, 400);
KUNIT_EXPECT_EQ(test, 0, ret);
KUNIT_EXPECT_EQ(test, 786, test_move_addr);
KUNIT_EXPECT_EQ(test, -8, test_move_offset);
KUNIT_EXPECT_EQ(test, 6, test_move_count);
KUNIT_EXPECT_EQ(test, 786, test_init_start);
KUNIT_EXPECT_EQ(test, 8, test_init_count);
KUNIT_EXPECT_EQ(test, 794, admin.last_used_addr);
}
static struct kunit_case vcap_api_rule_remove_test_cases[] = {
KUNIT_CASE(vcap_api_rule_remove_at_end_test),
KUNIT_CASE(vcap_api_rule_remove_in_middle_test),
KUNIT_CASE(vcap_api_rule_remove_in_front_test),
{}
};
static void vcap_api_next_lookup_basic_test(struct kunit *test)
{
struct vcap_admin admin1 = {
......@@ -1458,6 +1954,33 @@ static void vcap_api_next_lookup_advanced_test(struct kunit *test)
KUNIT_EXPECT_EQ(test, true, ret);
}
static struct kunit_suite vcap_api_rule_remove_test_suite = {
.name = "VCAP_API_Rule_Remove_Testsuite",
.test_cases = vcap_api_rule_remove_test_cases,
};
static struct kunit_case vcap_api_rule_insert_test_cases[] = {
KUNIT_CASE(vcap_api_rule_insert_in_order_test),
KUNIT_CASE(vcap_api_rule_insert_reverse_order_test),
{}
};
static struct kunit_suite vcap_api_rule_insert_test_suite = {
.name = "VCAP_API_Rule_Insert_Testsuite",
.test_cases = vcap_api_rule_insert_test_cases,
};
static struct kunit_case vcap_api_rule_counter_test_cases[] = {
KUNIT_CASE(vcap_api_set_rule_counter_test),
KUNIT_CASE(vcap_api_get_rule_counter_test),
{}
};
static struct kunit_suite vcap_api_rule_counter_test_suite = {
.name = "VCAP_API_Rule_Counter_Testsuite",
.test_cases = vcap_api_rule_counter_test_cases,
};
static struct kunit_case vcap_api_support_test_cases[] = {
KUNIT_CASE(vcap_api_next_lookup_basic_test),
KUNIT_CASE(vcap_api_next_lookup_advanced_test),
......@@ -1519,6 +2042,9 @@ static struct kunit_suite vcap_api_encoding_test_suite = {
.test_cases = vcap_api_encoding_test_cases,
};
kunit_test_suite(vcap_api_rule_remove_test_suite);
kunit_test_suite(vcap_api_rule_insert_test_suite);
kunit_test_suite(vcap_api_rule_counter_test_suite);
kunit_test_suite(vcap_api_support_test_suite);
kunit_test_suite(vcap_api_full_rule_test_suite);
kunit_test_suite(vcap_api_rule_value_test_suite);
......
......@@ -32,6 +32,10 @@ struct flow_match_vlan {
struct flow_dissector_key_vlan *key, *mask;
};
struct flow_match_arp {
struct flow_dissector_key_arp *key, *mask;
};
struct flow_match_ipv4_addrs {
struct flow_dissector_key_ipv4_addrs *key, *mask;
};
......@@ -98,6 +102,8 @@ void flow_rule_match_vlan(const struct flow_rule *rule,
struct flow_match_vlan *out);
void flow_rule_match_cvlan(const struct flow_rule *rule,
struct flow_match_vlan *out);
void flow_rule_match_arp(const struct flow_rule *rule,
struct flow_match_arp *out);
void flow_rule_match_ipv4_addrs(const struct flow_rule *rule,
struct flow_match_ipv4_addrs *out);
void flow_rule_match_ipv6_addrs(const struct flow_rule *rule,
......
......@@ -97,6 +97,13 @@ void flow_rule_match_cvlan(const struct flow_rule *rule,
}
EXPORT_SYMBOL(flow_rule_match_cvlan);
void flow_rule_match_arp(const struct flow_rule *rule,
struct flow_match_arp *out)
{
FLOW_DISSECTOR_MATCH(rule, FLOW_DISSECTOR_KEY_ARP, out);
}
EXPORT_SYMBOL(flow_rule_match_arp);
void flow_rule_match_ipv4_addrs(const struct flow_rule *rule,
struct flow_match_ipv4_addrs *out)
{
......
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