[PATCH net-next v8 05/15] net: sched: act_api: Add support for preallocated dynamic action instances

Jamal Hadi Salim
2023-11-16T15:00:01Z

In P4, actions are assumed to pre exist and have an upper bound number of
instances. Typically if you have 1M table entries you want to allocate
enough action instances to cover the 1M entries. However, this is a big
waste of memory if the action instances are not in use. So for our case,
we allow the user to specify a minimal amount of actions in the template
and then if more dynamic action instances are needed then they will be
added on demand as in the current approach with tc filter-action
relationship.

Add the necessary code to preallocate actions instances for dynamic
actions.

We add 2 new actions flags:
- TCA_ACT_FLAGS_PREALLOC: Indicates the action instance is a dynamic action
and was preallocated for future use the templating phase of P4TC
- TCA_ACT_FLAGS_UNREFERENCED: Indicates the action instance was
preallocated and is currently not being referenced by any other object.
Which means it won't show up in an action instance dump.

Once an action instance is created we don't free it when the last table
entry referring to it is deleted.
Instead we add it to the pool/cache of action instances for
that specific action i.e it counts as if it is preallocated.
Preallocated actions can't be deleted by the tc actions runtime commands
and a dump or a get will only show preallocated actions
instances which are being used (TCA_ACT_FLAGS_UNREFERENCED == false).

The preallocated actions will be deleted once the pipeline is deleted
(which will purge the dynamic action kind and its instances).

For example, if we were to create a dynamic action that preallocates 128
elements and dumped:

$ tc -j p4template get action/myprog/send_nh | jq .

We'd see the following:

[
{
"obj": "action template",
"pname": "myprog",
"pipeid": 1
},
{
"templates": [
{
"aname": "myprog/send_nh",
"actid": 1,
"params": [
{
"name": "port",
"type": "dev",
"id": 1
}
],
"prealloc": 128
}
]
}
]

If we try to dump the dynamic action instances, we won't see any:

$ tc -j actions ls action myprog/send_nh | jq .

[]

However, if we create a table entry which references this action kind:

$ tc p4ctrl create myprog/table/cb/FDB \
dstAddr d2:96:91:5d:02:86 action myprog/send_nh \
param port type dev dummy0

Dumping the action instance will now show this one instance which is
associated with the table entry:

$ tc -j actions ls action myprog/send_nh | jq .

[
{
"total acts": 1
},
{
"actions": [
{
"order": 0,
"kind": "myprog/send_nh",
"index": 1,
"ref": 1,
"bind": 1,
"params": [
{
"name": "port",
"type": "dev",
"value": "dummy0",
"id": 1
}
],
"not_in_hw": true
}
]
}
]

Co-developed-by: Victor Nogueira <victor@mojatatu.com>
Signed-off-by: Victor Nogueira <victor@mojatatu.com>
Co-developed-by: Pedro Tammela <pctammela@mojatatu.com>
Signed-off-by: Pedro Tammela <pctammela@mojatatu.com>
Signed-off-by: Jamal Hadi Salim <jhs@mojatatu.com>
Reviewed-by: Vlad Buslov <vladbu@nvidia.com>
---
include/net/act_api.h | 3 +++
net/sched/act_api.c | 50 ++++++++++++++++++++++++++++++++-----------
2 files changed, 41 insertions(+), 12 deletions(-)

diff --git a/include/net/act_api.h b/include/net/act_api.h
index 90e215f10..cd5a8e86f 100644
--- a/include/net/act_api.h
+++ b/include/net/act_api.h
@@ -68,6 +68,8 @@ struct tc_action {
#define TCA_ACT_FLAGS_REPLACE (1U << (TCA_ACT_FLAGS_USER_BITS + 2))
#define TCA_ACT_FLAGS_NO_RTNL (1U << (TCA_ACT_FLAGS_USER_BITS + 3))
#define TCA_ACT_FLAGS_AT_INGRESS (1U << (TCA_ACT_FLAGS_USER_BITS + 4))
+#define TCA_ACT_FLAGS_PREALLOC (1U << (TCA_ACT_FLAGS_USER_BITS + 5))
+#define TCA_ACT_FLAGS_UNREFERENCED (1U << (TCA_ACT_FLAGS_USER_BITS + 6))

/* Update lastuse only if needed, to avoid dirtying a cache line.
* We use a temp variable to avoid fetching jiffies twice.
@@ -200,6 +202,7 @@ int tcf_idr_create_from_flags(struct tc_action_net *tn, u32 index,
const struct tc_action_ops *ops, int bind,
u32 flags);
void tcf_idr_insert_many(struct tc_action *actions[]);
+void tcf_idr_insert_n(struct tc_action *actions[], const u32 n);
void tcf_idr_cleanup(struct tc_action_net *tn, u32 index);
int tcf_idr_check_alloc(struct tc_action_net *tn, u32 *index,
struct tc_action **a, int bind);
diff --git a/net/sched/act_api.c b/net/sched/act_api.c
index b277accc3..3fe399384 100644
--- a/net/sched/act_api.c
+++ b/net/sched/act_api.c
@@ -560,6 +560,8 @@ static int tcf_dump_walker(struct tcf_idrinfo *idrinfo, struct sk_buff *skb,
continue;
if (IS_ERR(p))
continue;
+ if (p->tcfa_flags & TCA_ACT_FLAGS_UNREFERENCED)
+ continue;

if (jiffy_since &&
time_after(jiffy_since,
@@ -640,6 +642,9 @@ static int tcf_del_walker(struct tcf_idrinfo *idrinfo, struct sk_buff *skb,
idr_for_each_entry_ul(idr, p, tmp, id) {
if (IS_ERR(p))
continue;
+ if (p->tcfa_flags & TCA_ACT_FLAGS_PREALLOC)
+ continue;
+
ret = tcf_idr_release_unsafe(p);
if (ret == ACT_P_DELETED)
module_put(ops->owner);
@@ -1367,26 +1372,38 @@ static const struct nla_policy tcf_action_policy[TCA_ACT_MAX + 1] = {
[TCA_ACT_HW_STATS] = NLA_POLICY_BITFIELD32(TCA_ACT_HW_STATS_ANY),
};

+static void tcf_idr_insert_1(struct tc_action *a)
+{
+ struct tcf_idrinfo *idrinfo;
+
+ idrinfo = a->idrinfo;
+ mutex_lock(&idrinfo->lock);
+ /* Replace ERR_PTR(-EBUSY) allocated by tcf_idr_check_alloc if
+ * it is just created, otherwise this is just a nop.
+ */
+ idr_replace(&idrinfo->action_idr, a, a->tcfa_index);
+ mutex_unlock(&idrinfo->lock);
+}
+
void tcf_idr_insert_many(struct tc_action *actions[])
{
int i;

for (i = 0; i < TCA_ACT_MAX_PRIO; i++) {
- struct tc_action *a = actions[i];
- struct tcf_idrinfo *idrinfo;
-
- if (!a)
+ if (!actions[i])
continue;
- idrinfo = a->idrinfo;
- mutex_lock(&idrinfo->lock);
- /* Replace ERR_PTR(-EBUSY) allocated by tcf_idr_check_alloc if
- * it is just created, otherwise this is just a nop.
- */
- idr_replace(&idrinfo->action_idr, a, a->tcfa_index);
- mutex_unlock(&idrinfo->lock);
+ tcf_idr_insert_1(actions[i]);
}
}

+void tcf_idr_insert_n(struct tc_action *actions[], const u32 n)
+{
+ int i;
+
+ for (i = 0; i < n; i++)
+ tcf_idr_insert_1(actions[i]);
+}
+
struct tc_action_ops *tc_action_load_ops(struct net *net, struct nlattr *nla,
bool police, bool rtnl_held,
struct netlink_ext_ack *extack)
@@ -2033,8 +2050,17 @@ tca_action_gd(struct net *net, struct nlattr *nla, struct nlmsghdr *n,
ret = PTR_ERR(act);
goto err;
}
- attr_size += tcf_action_fill_size(act);
actions[i - 1] = act;
+
+ if (event == RTM_DELACTION &&
+ act->tcfa_flags & TCA_ACT_FLAGS_PREALLOC) {
+ ret = -EINVAL;
+ NL_SET_ERR_MSG_FMT(extack,
+ "Unable to delete preallocated action %s",
+ act->ops->kind);
+ goto err;
+ }
+ attr_size += tcf_action_fill_size(act);
}

attr_size = tcf_action_full_attrs_size(attr_size);
--
2.34.1