+int
+rte_swx_ctl_pipeline_selector_group_add(struct rte_swx_ctl_pipeline *ctl,
+ const char *selector_name,
+ uint32_t *group_id)
+{
+ struct selector *s;
+ uint32_t i;
+
+ /* Check input arguments. */
+ if (!ctl || !selector_name || !selector_name[0] || !group_id)
+ return -EINVAL;
+
+ s = selector_find(ctl, selector_name);
+ if (!s)
+ return -EINVAL;
+
+ /* Find an unused group. */
+ for (i = 0; i < s->info.n_groups_max; i++)
+ if (!s->groups_added[i]) {
+ *group_id = i;
+ s->groups_added[i] = 1;
+ return 0;
+ }
+
+ return -ENOSPC;
+}
+
+int
+rte_swx_ctl_pipeline_selector_group_delete(struct rte_swx_ctl_pipeline *ctl,
+ const char *selector_name,
+ uint32_t group_id)
+{
+ struct selector *s;
+ struct rte_swx_table_selector_group *group;
+
+ /* Check input arguments. */
+ if (!ctl || !selector_name || !selector_name[0])
+ return -EINVAL;
+
+ s = selector_find(ctl, selector_name);
+ if (!s ||
+ (group_id >= s->info.n_groups_max) ||
+ !s->groups_added[group_id])
+ return -EINVAL;
+
+ /* Check if this group is already scheduled for deletion. */
+ if (s->groups_pending_delete[group_id])
+ return 0;
+
+ /* Initialize the pending group, if needed. */
+ if (!s->pending_groups[group_id]) {
+ int status;
+
+ status = selector_group_duplicate_to_pending(s, group_id);
+ if (status)
+ return status;
+ }
+
+ group = s->pending_groups[group_id];
+
+ /* Schedule removal of all the members from the current group. */
+ for ( ; ; ) {
+ struct rte_swx_table_selector_member *m;
+
+ m = TAILQ_FIRST(&group->members);
+ if (!m)
+ break;
+
+ TAILQ_REMOVE(&group->members, m, node);
+ free(m);
+ }
+
+ /* Schedule the group for deletion. */
+ s->groups_pending_delete[group_id] = 1;
+
+ return 0;
+}
+
+int
+rte_swx_ctl_pipeline_selector_group_member_add(struct rte_swx_ctl_pipeline *ctl,
+ const char *selector_name,
+ uint32_t group_id,
+ uint32_t member_id,
+ uint32_t member_weight)
+{
+ struct selector *s;
+ struct rte_swx_table_selector_group *group;
+ struct rte_swx_table_selector_member *m;
+
+ if (!member_weight)
+ return rte_swx_ctl_pipeline_selector_group_member_delete(ctl,
+ selector_name,
+ group_id,
+ member_id);
+
+ /* Check input arguments. */
+ if (!ctl || !selector_name || !selector_name[0])
+ return -EINVAL;
+
+ s = selector_find(ctl, selector_name);
+ if (!s ||
+ (group_id >= s->info.n_groups_max) ||
+ !s->groups_added[group_id] ||
+ s->groups_pending_delete[group_id])
+ return -EINVAL;
+
+ /* Initialize the pending group, if needed. */
+ if (!s->pending_groups[group_id]) {
+ int status;
+
+ status = selector_group_duplicate_to_pending(s, group_id);
+ if (status)
+ return status;
+ }
+
+ group = s->pending_groups[group_id];
+
+ /* If this member is already in this group, then simply update its weight and return. */
+ TAILQ_FOREACH(m, &group->members, node)
+ if (m->member_id == member_id) {
+ m->member_weight = member_weight;
+ return 0;
+ }
+
+ /* Add new member to this group. */
+ m = calloc(1, sizeof(struct rte_swx_table_selector_member));
+ if (!m)
+ return -ENOMEM;
+
+ m->member_id = member_id;
+ m->member_weight = member_weight;
+
+ TAILQ_INSERT_TAIL(&group->members, m, node);
+
+ return 0;
+}
+
+int
+rte_swx_ctl_pipeline_selector_group_member_delete(struct rte_swx_ctl_pipeline *ctl,
+ const char *selector_name,
+ uint32_t group_id __rte_unused,
+ uint32_t member_id __rte_unused)
+{
+ struct selector *s;
+ struct rte_swx_table_selector_group *group;
+ struct rte_swx_table_selector_member *m;
+
+ /* Check input arguments. */
+ if (!ctl || !selector_name || !selector_name[0])
+ return -EINVAL;
+
+ s = selector_find(ctl, selector_name);
+ if (!s ||
+ (group_id >= s->info.n_groups_max) ||
+ !s->groups_added[group_id] ||
+ s->groups_pending_delete[group_id])
+ return -EINVAL;
+
+ /* Initialize the pending group, if needed. */
+ if (!s->pending_groups[group_id]) {
+ int status;
+
+ status = selector_group_duplicate_to_pending(s, group_id);
+ if (status)
+ return status;
+ }
+
+ group = s->pending_groups[group_id];
+
+ /* Look for this member in the group and remove it, if found. */
+ TAILQ_FOREACH(m, &group->members, node)
+ if (m->member_id == member_id) {
+ TAILQ_REMOVE(&group->members, m, node);
+ free(m);
+ return 0;
+ }
+
+ return 0;
+}
+
+static int
+selector_rollfwd(struct rte_swx_ctl_pipeline *ctl, uint32_t selector_id)
+{
+ struct selector *s = &ctl->selectors[selector_id];
+ struct rte_swx_table_state *ts_next = &ctl->ts_next[ctl->info.n_tables + selector_id];
+ uint32_t group_id;
+
+ /* Push pending group member changes (s->pending_groups[group_id]) to the selector table
+ * mirror copy (ts_next->obj).
+ */
+ for (group_id = 0; group_id < s->info.n_groups_max; group_id++) {
+ struct rte_swx_table_selector_group *group = s->pending_groups[group_id];
+ int status;
+
+ /* Skip this group if no change needed. */
+ if (!group)
+ continue;
+
+ /* Apply the pending changes for the current group. */
+ status = rte_swx_table_selector_group_set(ts_next->obj, group_id, group);
+ if (status)
+ return status;
+ }
+
+ return 0;
+}
+
+static void
+selector_rollfwd_finalize(struct rte_swx_ctl_pipeline *ctl, uint32_t selector_id)
+{
+ struct selector *s = &ctl->selectors[selector_id];
+ uint32_t group_id;
+
+ /* Commit pending group member changes (s->pending_groups[group_id]) to the stable group
+ * records (s->groups[group_id).
+ */
+ for (group_id = 0; group_id < s->info.n_groups_max; group_id++) {
+ struct rte_swx_table_selector_group *g = s->groups[group_id];
+ struct rte_swx_table_selector_group *gp = s->pending_groups[group_id];
+
+ /* Skip this group if no change needed. */
+ if (!gp)
+ continue;
+
+ /* Transition the pending changes to stable. */
+ s->groups[group_id] = gp;
+ s->pending_groups[group_id] = NULL;
+
+ /* Free the old group member list. */
+ if (!g)
+ continue;
+
+ for ( ; ; ) {
+ struct rte_swx_table_selector_member *m;
+
+ m = TAILQ_FIRST(&g->members);
+ if (!m)
+ break;
+
+ TAILQ_REMOVE(&g->members, m, node);
+ free(m);
+ }
+
+ free(g);
+ }
+
+ /* Commit pending group validity changes (from s->groups_pending_delete[group_id] to
+ * s->groups_added[group_id].
+ */
+ for (group_id = 0; group_id < s->info.n_groups_max; group_id++)
+ if (s->groups_pending_delete[group_id]) {
+ s->groups_added[group_id] = 0;
+ s->groups_pending_delete[group_id] = 0;
+ }
+}
+
+static void
+selector_rollback(struct rte_swx_ctl_pipeline *ctl, uint32_t selector_id)
+{
+ struct selector *s = &ctl->selectors[selector_id];
+ struct rte_swx_table_state *ts = &ctl->ts[ctl->info.n_tables + selector_id];
+ struct rte_swx_table_state *ts_next = &ctl->ts_next[ctl->info.n_tables + selector_id];
+ uint32_t group_id;
+
+ /* Discard any previous changes to the selector table mirror copy (ts_next->obj). */
+ for (group_id = 0; group_id < s->info.n_groups_max; group_id++) {
+ struct rte_swx_table_selector_group *gp = s->pending_groups[group_id];
+
+ if (gp) {
+ ts_next->obj = ts->obj;
+ break;
+ }
+ }
+}
+
+static void
+selector_abort(struct rte_swx_ctl_pipeline *ctl, uint32_t selector_id)
+{
+ struct selector *s = &ctl->selectors[selector_id];
+ uint32_t group_id;
+
+ /* Discard any pending group member changes (s->pending_groups[group_id]). */
+ for (group_id = 0; group_id < s->info.n_groups_max; group_id++)
+ selector_pending_group_members_free(s, group_id);
+
+ /* Discard any pending group deletions. */
+ memset(s->groups_pending_delete, 0, s->info.n_groups_max * sizeof(int));
+}
+
+static struct rte_swx_table_entry *
+learner_default_entry_alloc(struct learner *l)
+{
+ struct rte_swx_table_entry *entry;
+
+ entry = calloc(1, sizeof(struct rte_swx_table_entry));
+ if (!entry)
+ goto error;
+
+ /* action_data. */
+ if (l->action_data_size) {
+ entry->action_data = calloc(1, l->action_data_size);
+ if (!entry->action_data)
+ goto error;
+ }
+
+ return entry;
+
+error:
+ table_entry_free(entry);
+ return NULL;
+}
+
+static int
+learner_default_entry_check(struct rte_swx_ctl_pipeline *ctl,
+ uint32_t learner_id,
+ struct rte_swx_table_entry *entry)
+{
+ struct learner *l = &ctl->learners[learner_id];
+ struct action *a;
+ uint32_t i;
+
+ CHECK(entry, EINVAL);
+
+ /* action_id. */
+ for (i = 0; i < l->info.n_actions; i++)
+ if (entry->action_id == l->actions[i].action_id)
+ break;
+
+ CHECK(i < l->info.n_actions, EINVAL);
+
+ /* action_data. */
+ a = &ctl->actions[entry->action_id];
+ CHECK(!(a->data_size && !entry->action_data), EINVAL);
+
+ return 0;
+}
+
+static struct rte_swx_table_entry *
+learner_default_entry_duplicate(struct rte_swx_ctl_pipeline *ctl,
+ uint32_t learner_id,
+ struct rte_swx_table_entry *entry)
+{
+ struct learner *l = &ctl->learners[learner_id];
+ struct rte_swx_table_entry *new_entry = NULL;
+ struct action *a;
+ uint32_t i;
+
+ if (!entry)
+ goto error;
+
+ new_entry = calloc(1, sizeof(struct rte_swx_table_entry));
+ if (!new_entry)
+ goto error;
+
+ /* action_id. */
+ for (i = 0; i < l->info.n_actions; i++)
+ if (entry->action_id == l->actions[i].action_id)
+ break;
+
+ if (i >= l->info.n_actions)
+ goto error;
+
+ new_entry->action_id = entry->action_id;
+
+ /* action_data. */
+ a = &ctl->actions[entry->action_id];
+ if (a->data_size && !entry->action_data)
+ goto error;
+
+ /* The table layer provisions a constant action data size per
+ * entry, which should be the largest data size for all the
+ * actions enabled for the current table, and attempts to copy
+ * this many bytes each time a table entry is added, even if the
+ * specific action requires less data or even no data at all,
+ * hence we always have to allocate the max.
+ */
+ new_entry->action_data = calloc(1, l->action_data_size);
+ if (!new_entry->action_data)
+ goto error;
+
+ if (a->data_size)
+ memcpy(new_entry->action_data, entry->action_data, a->data_size);
+
+ return new_entry;
+
+error:
+ table_entry_free(new_entry);
+ return NULL;
+}
+
+int
+rte_swx_ctl_pipeline_learner_default_entry_add(struct rte_swx_ctl_pipeline *ctl,
+ const char *learner_name,
+ struct rte_swx_table_entry *entry)
+{
+ struct learner *l;
+ struct rte_swx_table_entry *new_entry;
+ uint32_t learner_id;
+
+ CHECK(ctl, EINVAL);
+
+ CHECK(learner_name && learner_name[0], EINVAL);
+ l = learner_find(ctl, learner_name);
+ CHECK(l, EINVAL);
+ learner_id = l - ctl->learners;
+ CHECK(!l->info.default_action_is_const, EINVAL);
+
+ CHECK(entry, EINVAL);
+ CHECK(!learner_default_entry_check(ctl, learner_id, entry), EINVAL);
+
+ CHECK(l->actions[entry->action_id].action_is_for_default_entry, EINVAL);
+
+ new_entry = learner_default_entry_duplicate(ctl, learner_id, entry);
+ CHECK(new_entry, ENOMEM);
+
+ learner_pending_default_free(l);
+
+ l->pending_default = new_entry;
+ return 0;
+}
+
+static void
+learner_rollfwd(struct rte_swx_ctl_pipeline *ctl, uint32_t learner_id)
+{
+ struct learner *l = &ctl->learners[learner_id];
+ struct rte_swx_table_state *ts_next = &ctl->ts_next[ctl->info.n_tables +
+ ctl->info.n_selectors + learner_id];
+ struct action *a;
+ uint8_t *action_data;
+ uint64_t action_id;
+
+ /* Copy the pending default entry. */
+ if (!l->pending_default)
+ return;
+
+ action_id = l->pending_default->action_id;
+ action_data = l->pending_default->action_data;
+ a = &ctl->actions[action_id];
+
+ if (a->data_size)
+ memcpy(ts_next->default_action_data, action_data, a->data_size);
+
+ ts_next->default_action_id = action_id;
+}
+
+static void
+learner_rollfwd_finalize(struct rte_swx_ctl_pipeline *ctl, uint32_t learner_id)
+{
+ struct learner *l = &ctl->learners[learner_id];
+
+ /* Free up the pending default entry, as it is now part of the table. */
+ learner_pending_default_free(l);
+}
+
+static void
+learner_abort(struct rte_swx_ctl_pipeline *ctl, uint32_t learner_id)
+{
+ struct learner *l = &ctl->learners[learner_id];
+
+ /* Free up the pending default entry, as it is no longer going to be added to the table. */
+ learner_pending_default_free(l);
+}
+