Add pipeline level support for learner tables.
Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
struct rte_swx_table_selector_params params;
};
+struct learner {
+ struct rte_swx_ctl_learner_info info;
+ struct rte_swx_ctl_table_match_field_info *mf;
+ struct rte_swx_ctl_table_action_info *actions;
+ uint32_t action_data_size;
+
+ /* The pending default action: this is NOT the current default action;
+ * this will be the new default action after the next commit, if the
+ * next commit operation is successful.
+ */
+ struct rte_swx_table_entry *pending_default;
+};
+
struct rte_swx_ctl_pipeline {
struct rte_swx_ctl_pipeline_info info;
struct rte_swx_pipeline *p;
struct action *actions;
struct table *tables;
struct selector *selectors;
+ struct learner *learners;
struct rte_swx_table_state *ts;
struct rte_swx_table_state *ts_next;
int numa_node;
return 0;
}
+static void
+learner_pending_default_free(struct learner *l)
+{
+ if (!l->pending_default)
+ return;
+
+ free(l->pending_default->action_data);
+ free(l->pending_default);
+ l->pending_default = NULL;
+}
+
+
+static void
+learner_free(struct rte_swx_ctl_pipeline *ctl)
+{
+ uint32_t i;
+
+ if (!ctl->learners)
+ return;
+
+ for (i = 0; i < ctl->info.n_learners; i++) {
+ struct learner *l = &ctl->learners[i];
+
+ free(l->mf);
+ free(l->actions);
+
+ learner_pending_default_free(l);
+ }
+
+ free(ctl->learners);
+ ctl->learners = NULL;
+}
+
+static struct learner *
+learner_find(struct rte_swx_ctl_pipeline *ctl, const char *learner_name)
+{
+ uint32_t i;
+
+ for (i = 0; i < ctl->info.n_learners; i++) {
+ struct learner *l = &ctl->learners[i];
+
+ if (!strcmp(learner_name, l->info.name))
+ return l;
+ }
+
+ return NULL;
+}
+
+static uint32_t
+learner_action_data_size_get(struct rte_swx_ctl_pipeline *ctl, struct learner *l)
+{
+ uint32_t action_data_size = 0, i;
+
+ for (i = 0; i < l->info.n_actions; i++) {
+ uint32_t action_id = l->actions[i].action_id;
+ struct action *a = &ctl->actions[action_id];
+
+ if (a->data_size > action_data_size)
+ action_data_size = a->data_size;
+ }
+
+ return action_data_size;
+}
+
static void
table_state_free(struct rte_swx_ctl_pipeline *ctl)
{
rte_swx_table_selector_free(ts->obj);
}
+ /* For each learner table, free its table state. */
+ for (i = 0; i < ctl->info.n_learners; i++) {
+ struct rte_swx_table_state *ts = &ctl->ts_next[i];
+
+ /* Default action data. */
+ free(ts->default_action_data);
+ }
+
free(ctl->ts_next);
ctl->ts_next = NULL;
}
}
}
+ /* Learner tables. */
+ for (i = 0; i < ctl->info.n_learners; i++) {
+ struct learner *l = &ctl->learners[i];
+ struct rte_swx_table_state *ts = &ctl->ts[i];
+ struct rte_swx_table_state *ts_next = &ctl->ts_next[i];
+
+ /* Table object: duplicate from the current table state. */
+ ts_next->obj = ts->obj;
+
+ /* Default action data: duplicate from the current table state. */
+ ts_next->default_action_data = malloc(l->action_data_size);
+ if (!ts_next->default_action_data) {
+ status = -ENOMEM;
+ goto error;
+ }
+
+ memcpy(ts_next->default_action_data,
+ ts->default_action_data,
+ l->action_data_size);
+
+ ts_next->default_action_id = ts->default_action_id;
+ }
+
return 0;
error:
table_state_free(ctl);
+ learner_free(ctl);
+
selector_free(ctl);
table_free(ctl);
goto error;
}
+ /* learner tables. */
+ ctl->learners = calloc(ctl->info.n_learners, sizeof(struct learner));
+ if (!ctl->learners)
+ goto error;
+
+ for (i = 0; i < ctl->info.n_learners; i++) {
+ struct learner *l = &ctl->learners[i];
+ uint32_t j;
+
+ /* info. */
+ status = rte_swx_ctl_learner_info_get(p, i, &l->info);
+ if (status)
+ goto error;
+
+ /* mf. */
+ l->mf = calloc(l->info.n_match_fields,
+ sizeof(struct rte_swx_ctl_table_match_field_info));
+ if (!l->mf)
+ goto error;
+
+ for (j = 0; j < l->info.n_match_fields; j++) {
+ status = rte_swx_ctl_learner_match_field_info_get(p,
+ i,
+ j,
+ &l->mf[j]);
+ if (status)
+ goto error;
+ }
+
+ /* actions. */
+ l->actions = calloc(l->info.n_actions,
+ sizeof(struct rte_swx_ctl_table_action_info));
+ if (!l->actions)
+ goto error;
+
+ for (j = 0; j < l->info.n_actions; j++) {
+ status = rte_swx_ctl_learner_action_info_get(p,
+ i,
+ j,
+ &l->actions[j]);
+ if (status || l->actions[j].action_id >= ctl->info.n_actions)
+ goto error;
+ }
+
+ /* action_data_size. */
+ l->action_data_size = learner_action_data_size_get(ctl, l);
+ }
+
/* ts. */
status = rte_swx_pipeline_table_state_get(p, &ctl->ts);
if (status)
action_data = table->pending_default->action_data;
a = &ctl->actions[action_id];
- memcpy(ts_next->default_action_data,
- action_data,
- a->data_size);
+ if (a->data_size)
+ memcpy(ts_next->default_action_data, action_data, a->data_size);
ts_next->default_action_id = action_id;
}
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);
+
+ 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);
+}
+
int
rte_swx_ctl_pipeline_commit(struct rte_swx_ctl_pipeline *ctl, int abort_on_fail)
{
/* Operate the changes on the current ts_next before it becomes the new ts. First, operate
* all the changes that can fail; if no failure, then operate the changes that cannot fail.
+ * We must be able to fully revert all the changes that can fail as if they never happened.
*/
for (i = 0; i < ctl->info.n_tables; i++) {
status = table_rollfwd0(ctl, i, 0);
goto rollback;
}
+ /* Second, operate all the changes that cannot fail. Since nothing can fail from this point
+ * onwards, the transaction is guaranteed to be successful.
+ */
for (i = 0; i < ctl->info.n_tables; i++)
table_rollfwd1(ctl, i);
+ for (i = 0; i < ctl->info.n_learners; i++)
+ learner_rollfwd(ctl, i);
+
/* Swap the table state for the data plane. The current ts and ts_next
* become the new ts_next and ts, respectively.
*/
selector_rollfwd_finalize(ctl, i);
}
+ for (i = 0; i < ctl->info.n_learners; i++) {
+ learner_rollfwd(ctl, i);
+ learner_rollfwd_finalize(ctl, i);
+ }
+
return 0;
rollback:
selector_abort(ctl, i);
}
+ if (abort_on_fail)
+ for (i = 0; i < ctl->info.n_learners; i++)
+ learner_abort(ctl, i);
+
return status;
}
for (i = 0; i < ctl->info.n_selectors; i++)
selector_abort(ctl, i);
+
+ for (i = 0; i < ctl->info.n_learners; i++)
+ learner_abort(ctl, i);
}
static int
return NULL;
}
+struct rte_swx_table_entry *
+rte_swx_ctl_pipeline_learner_default_entry_read(struct rte_swx_ctl_pipeline *ctl,
+ const char *learner_name,
+ const char *string,
+ int *is_blank_or_comment)
+{
+ char *token_array[RTE_SWX_CTL_ENTRY_TOKENS_MAX], **tokens;
+ struct learner *l;
+ struct action *action;
+ struct rte_swx_table_entry *entry = NULL;
+ char *s0 = NULL, *s;
+ uint32_t n_tokens = 0, arg_offset = 0, i;
+ int blank_or_comment = 0;
+
+ /* Check input arguments. */
+ if (!ctl)
+ goto error;
+
+ if (!learner_name || !learner_name[0])
+ goto error;
+
+ l = learner_find(ctl, learner_name);
+ if (!l)
+ goto error;
+
+ if (!string || !string[0])
+ goto error;
+
+ /* Memory allocation. */
+ s0 = strdup(string);
+ if (!s0)
+ goto error;
+
+ entry = learner_default_entry_alloc(l);
+ if (!entry)
+ goto error;
+
+ /* Parse the string into tokens. */
+ for (s = s0; ; ) {
+ char *token;
+
+ token = strtok_r(s, " \f\n\r\t\v", &s);
+ if (!token || token_is_comment(token))
+ break;
+
+ if (n_tokens >= RTE_SWX_CTL_ENTRY_TOKENS_MAX)
+ goto error;
+
+ token_array[n_tokens] = token;
+ n_tokens++;
+ }
+
+ if (!n_tokens) {
+ blank_or_comment = 1;
+ goto error;
+ }
+
+ tokens = token_array;
+
+ /*
+ * Action.
+ */
+ if (!(n_tokens && !strcmp(tokens[0], "action")))
+ goto other;
+
+ if (n_tokens < 2)
+ goto error;
+
+ action = action_find(ctl, tokens[1]);
+ if (!action)
+ goto error;
+
+ if (n_tokens < 2 + action->info.n_args * 2)
+ goto error;
+
+ /* action_id. */
+ entry->action_id = action - ctl->actions;
+
+ /* action_data. */
+ for (i = 0; i < action->info.n_args; i++) {
+ struct rte_swx_ctl_action_arg_info *arg = &action->args[i];
+ char *arg_name, *arg_val;
+ uint64_t val;
+
+ arg_name = tokens[2 + i * 2];
+ arg_val = tokens[2 + i * 2 + 1];
+
+ if (strcmp(arg_name, arg->name))
+ goto error;
+
+ val = strtoull(arg_val, &arg_val, 0);
+ if (arg_val[0])
+ goto error;
+
+ /* Endianness conversion. */
+ if (arg->is_network_byte_order)
+ val = field_hton(val, arg->n_bits);
+
+ /* Copy to entry. */
+ memcpy(&entry->action_data[arg_offset],
+ (uint8_t *)&val,
+ arg->n_bits / 8);
+
+ arg_offset += arg->n_bits / 8;
+ }
+
+ tokens += 2 + action->info.n_args * 2;
+ n_tokens -= 2 + action->info.n_args * 2;
+
+other:
+ if (n_tokens)
+ goto error;
+
+ free(s0);
+ return entry;
+
+error:
+ table_entry_free(entry);
+ free(s0);
+ if (is_blank_or_comment)
+ *is_blank_or_comment = blank_or_comment;
+ return NULL;
+}
+
static void
table_entry_printf(FILE *f,
struct rte_swx_ctl_pipeline *ctl,
/** Number of selector tables. */
uint32_t n_selectors;
+ /** Number of learner tables. */
+ uint32_t n_learners;
+
/** Number of register arrays. */
uint32_t n_regarrays;
const char *selector_name,
struct rte_swx_pipeline_selector_stats *stats);
+/*
+ * Learner Table Query API.
+ */
+
+/** Learner table info. */
+struct rte_swx_ctl_learner_info {
+ /** Learner table name. */
+ char name[RTE_SWX_CTL_NAME_SIZE];
+
+ /** Number of match fields. */
+ uint32_t n_match_fields;
+
+ /** Number of actions. */
+ uint32_t n_actions;
+
+ /** Non-zero (true) when the default action is constant, therefore it
+ * cannot be changed; zero (false) when the default action not constant,
+ * therefore it can be changed.
+ */
+ int default_action_is_const;
+
+ /** Learner table size parameter. */
+ uint32_t size;
+};
+
+/**
+ * Learner table info get
+ *
+ * @param[in] p
+ * Pipeline handle.
+ * @param[in] learner_id
+ * Learner table ID (0 .. *n_learners* - 1).
+ * @param[out] learner
+ * Learner table info.
+ * @return
+ * 0 on success or the following error codes otherwise:
+ * -EINVAL: Invalid argument.
+ */
+__rte_experimental
+int
+rte_swx_ctl_learner_info_get(struct rte_swx_pipeline *p,
+ uint32_t learner_id,
+ struct rte_swx_ctl_learner_info *learner);
+
+/**
+ * Learner table match field info get
+ *
+ * @param[in] p
+ * Pipeline handle.
+ * @param[in] learner_id
+ * Learner table ID (0 .. *n_learners* - 1).
+ * @param[in] match_field_id
+ * Match field ID (0 .. *n_match_fields* - 1).
+ * @param[out] match_field
+ * Learner table match field info.
+ * @return
+ * 0 on success or the following error codes otherwise:
+ * -EINVAL: Invalid argument.
+ */
+__rte_experimental
+int
+rte_swx_ctl_learner_match_field_info_get(struct rte_swx_pipeline *p,
+ uint32_t learner_id,
+ uint32_t match_field_id,
+ struct rte_swx_ctl_table_match_field_info *match_field);
+
+/**
+ * Learner table action info get
+ *
+ * @param[in] p
+ * Pipeline handle.
+ * @param[in] learner_id
+ * Learner table ID (0 .. *n_learners* - 1).
+ * @param[in] learner_action_id
+ * Action index within the set of learner table actions (0 .. learner table n_actions - 1). Not
+ * to be confused with the pipeline-leve action ID (0 .. pipeline n_actions - 1), which is
+ * precisely what this function returns as part of the *learner_action*.
+ * @param[out] learner_action
+ * Learner action info.
+ * @return
+ * 0 on success or the following error codes otherwise:
+ * -EINVAL: Invalid argument.
+ */
+__rte_experimental
+int
+rte_swx_ctl_learner_action_info_get(struct rte_swx_pipeline *p,
+ uint32_t learner_id,
+ uint32_t learner_action_id,
+ struct rte_swx_ctl_table_action_info *learner_action);
+
+/** Learner table statistics. */
+struct rte_swx_learner_stats {
+ /** Number of packets with lookup hit. */
+ uint64_t n_pkts_hit;
+
+ /** Number of packets with lookup miss. */
+ uint64_t n_pkts_miss;
+
+ /** Number of packets with successful learning. */
+ uint64_t n_pkts_learn_ok;
+
+ /** Number of packets with learning error. */
+ uint64_t n_pkts_learn_err;
+
+ /** Number of packets with forget event. */
+ uint64_t n_pkts_forget;
+
+ /** Number of packets (with either lookup hit or miss) per pipeline action. Array of
+ * pipeline *n_actions* elements indedex by the pipeline-level *action_id*, therefore this
+ * array has the same size for all the tables within the same pipeline.
+ */
+ uint64_t *n_pkts_action;
+};
+
+/**
+ * Learner table statistics counters read
+ *
+ * @param[in] p
+ * Pipeline handle.
+ * @param[in] learner_name
+ * Learner table name.
+ * @param[out] stats
+ * Learner table stats. Must point to a pre-allocated structure. The *n_pkts_action* field also
+ * needs to be pre-allocated as array of pipeline *n_actions* elements. The pipeline actions that
+ * are not valid for the current learner table have their associated *n_pkts_action* element
+ * always set to zero.
+ * @return
+ * 0 on success or the following error codes otherwise:
+ * -EINVAL: Invalid argument.
+ */
+__rte_experimental
+int
+rte_swx_ctl_pipeline_learner_stats_read(struct rte_swx_pipeline *p,
+ const char *learner_name,
+ struct rte_swx_learner_stats *stats);
+
/*
* Table Update API.
*/
uint32_t group_id,
uint32_t member_id);
+/**
+ * Pipeline learner table default entry add
+ *
+ * Schedule learner table default entry update as part of the next commit operation.
+ *
+ * @param[in] ctl
+ * Pipeline control handle.
+ * @param[in] learner_name
+ * Learner table name.
+ * @param[in] entry
+ * The new table default entry. The *key* and *key_mask* entry fields are ignored.
+ * @return
+ * 0 on success or the following error codes otherwise:
+ * -EINVAL: Invalid argument.
+ */
+__rte_experimental
+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);
+
/**
* Pipeline commit
*
const char *string,
int *is_blank_or_comment);
+/**
+ * Pipeline learner table default entry read
+ *
+ * Read learner table default entry from string.
+ *
+ * @param[in] ctl
+ * Pipeline control handle.
+ * @param[in] learner_name
+ * Learner table name.
+ * @param[in] string
+ * String containing the learner table default entry.
+ * @param[out] is_blank_or_comment
+ * On error, this argument provides an indication of whether *string* contains
+ * an invalid table entry (set to zero) or a blank or comment line that should
+ * typically be ignored (set to a non-zero value).
+ * @return
+ * 0 on success or the following error codes otherwise:
+ * -EINVAL: Invalid argument.
+ */
+__rte_experimental
+struct rte_swx_table_entry *
+rte_swx_ctl_pipeline_learner_default_entry_read(struct rte_swx_ctl_pipeline *ctl,
+ const char *learner_name,
+ const char *string,
+ int *is_blank_or_comment);
+
/**
* Pipeline table print to file
*
#include <rte_meter.h>
#include <rte_swx_table_selector.h>
+#include <rte_swx_table_learner.h>
#include "rte_swx_pipeline.h"
#include "rte_swx_ctl.h"
/* table TABLE */
INSTR_TABLE,
INSTR_SELECTOR,
+ INSTR_LEARNER,
+
+ /* learn LEARNER ACTION_NAME */
+ INSTR_LEARNER_LEARN,
+
+ /* forget */
+ INSTR_LEARNER_FORGET,
/* extern e.obj.func */
INSTR_EXTERN_OBJ,
uint8_t table_id;
};
+struct instr_learn {
+ uint8_t action_id;
+};
+
struct instr_extern_obj {
uint8_t ext_obj_id;
uint8_t func_id;
struct instr_dma dma;
struct instr_dst_src alu;
struct instr_table table;
+ struct instr_learn learn;
struct instr_extern_obj ext_obj;
struct instr_extern_func ext_func;
struct instr_jmp jmp;
TAILQ_ENTRY(action) node;
char name[RTE_SWX_NAME_SIZE];
struct struct_type *st;
- int *args_endianness; /* 0 = Host Byte Order (HBO). */
+ int *args_endianness; /* 0 = Host Byte Order (HBO); 1 = Network Byte Order (NBO). */
struct instruction *instructions;
uint32_t n_instructions;
uint32_t id;
uint64_t n_pkts;
};
+/*
+ * Learner table.
+ */
+struct learner {
+ TAILQ_ENTRY(learner) node;
+ char name[RTE_SWX_NAME_SIZE];
+
+ /* Match. */
+ struct field **fields;
+ uint32_t n_fields;
+ struct header *header;
+
+ /* Action. */
+ struct action **actions;
+ struct field **action_arg;
+ struct action *default_action;
+ uint8_t *default_action_data;
+ uint32_t n_actions;
+ int default_action_is_const;
+ uint32_t action_data_size_max;
+
+ uint32_t size;
+ uint32_t timeout;
+ uint32_t id;
+};
+
+TAILQ_HEAD(learner_tailq, learner);
+
+struct learner_runtime {
+ void *mailbox;
+ uint8_t **key;
+ uint8_t **action_data;
+};
+
+struct learner_statistics {
+ uint64_t n_pkts_hit[2]; /* 0 = Miss, 1 = Hit. */
+ uint64_t n_pkts_learn[2]; /* 0 = Learn OK, 1 = Learn error. */
+ uint64_t n_pkts_forget;
+ uint64_t *n_pkts_action;
+};
+
/*
* Register array.
*/
/* Tables. */
struct table_runtime *tables;
struct selector_runtime *selectors;
+ struct learner_runtime *learners;
struct rte_swx_table_state *table_state;
uint64_t action_id;
int hit; /* 0 = Miss, 1 = Hit. */
+ uint32_t learner_id;
+ uint64_t time;
/* Extern objects and functions. */
struct extern_obj_runtime *extern_objs;
struct table_type_tailq table_types;
struct table_tailq tables;
struct selector_tailq selectors;
+ struct learner_tailq learners;
struct regarray_tailq regarrays;
struct meter_profile_tailq meter_profiles;
struct metarray_tailq metarrays;
struct rte_swx_table_state *table_state;
struct table_statistics *table_stats;
struct selector_statistics *selector_stats;
+ struct learner_statistics *learner_stats;
struct regarray_runtime *regarray_runtime;
struct metarray_runtime *metarray_runtime;
struct instruction *instructions;
uint32_t n_actions;
uint32_t n_tables;
uint32_t n_selectors;
+ uint32_t n_learners;
uint32_t n_regarrays;
uint32_t n_metarrays;
uint32_t n_headers;
static struct selector *
selector_find(struct rte_swx_pipeline *p, const char *name);
+static struct learner *
+learner_find(struct rte_swx_pipeline *p, const char *name);
+
static int
instr_table_translate(struct rte_swx_pipeline *p,
struct action *action,
{
struct table *t;
struct selector *s;
+ struct learner *l;
CHECK(!action, EINVAL);
CHECK(n_tokens == 2, EINVAL);
return 0;
}
+ l = learner_find(p, tokens[1]);
+ if (l) {
+ instr->type = INSTR_LEARNER;
+ instr->table.table_id = l->id;
+ return 0;
+ }
+
CHECK(0, EINVAL);
}
thread_ip_inc(p);
}
+static inline void
+instr_learner_exec(struct rte_swx_pipeline *p)
+{
+ struct thread *t = &p->threads[p->thread_id];
+ struct instruction *ip = t->ip;
+ uint32_t learner_id = ip->table.table_id;
+ struct rte_swx_table_state *ts = &t->table_state[p->n_tables +
+ p->n_selectors + learner_id];
+ struct learner_runtime *l = &t->learners[learner_id];
+ struct learner_statistics *stats = &p->learner_stats[learner_id];
+ uint64_t action_id, n_pkts_hit, n_pkts_action, time;
+ uint8_t *action_data;
+ int done, hit;
+
+ /* Table. */
+ time = rte_get_tsc_cycles();
+
+ done = rte_swx_table_learner_lookup(ts->obj,
+ l->mailbox,
+ time,
+ l->key,
+ &action_id,
+ &action_data,
+ &hit);
+ if (!done) {
+ /* Thread. */
+ TRACE("[Thread %2u] learner %u (not finalized)\n",
+ p->thread_id,
+ learner_id);
+
+ thread_yield(p);
+ return;
+ }
+
+ action_id = hit ? action_id : ts->default_action_id;
+ action_data = hit ? action_data : ts->default_action_data;
+ n_pkts_hit = stats->n_pkts_hit[hit];
+ n_pkts_action = stats->n_pkts_action[action_id];
+
+ TRACE("[Thread %2u] learner %u (%s, action %u)\n",
+ p->thread_id,
+ learner_id,
+ hit ? "hit" : "miss",
+ (uint32_t)action_id);
+
+ t->action_id = action_id;
+ t->structs[0] = action_data;
+ t->hit = hit;
+ t->learner_id = learner_id;
+ t->time = time;
+ stats->n_pkts_hit[hit] = n_pkts_hit + 1;
+ stats->n_pkts_action[action_id] = n_pkts_action + 1;
+
+ /* Thread. */
+ thread_ip_action_call(p, t, action_id);
+}
+
+/*
+ * learn.
+ */
+static struct action *
+action_find(struct rte_swx_pipeline *p, const char *name);
+
+static int
+action_has_nbo_args(struct action *a);
+
+static int
+instr_learn_translate(struct rte_swx_pipeline *p,
+ struct action *action,
+ char **tokens,
+ int n_tokens,
+ struct instruction *instr,
+ struct instruction_data *data __rte_unused)
+{
+ struct action *a;
+
+ CHECK(action, EINVAL);
+ CHECK(n_tokens == 2, EINVAL);
+
+ a = action_find(p, tokens[1]);
+ CHECK(a, EINVAL);
+ CHECK(!action_has_nbo_args(a), EINVAL);
+
+ instr->type = INSTR_LEARNER_LEARN;
+ instr->learn.action_id = a->id;
+
+ return 0;
+}
+
+static inline void
+instr_learn_exec(struct rte_swx_pipeline *p)
+{
+ struct thread *t = &p->threads[p->thread_id];
+ struct instruction *ip = t->ip;
+ uint64_t action_id = ip->learn.action_id;
+ uint32_t learner_id = t->learner_id;
+ struct rte_swx_table_state *ts = &t->table_state[p->n_tables +
+ p->n_selectors + learner_id];
+ struct learner_runtime *l = &t->learners[learner_id];
+ struct learner_statistics *stats = &p->learner_stats[learner_id];
+ uint32_t status;
+
+ /* Table. */
+ status = rte_swx_table_learner_add(ts->obj,
+ l->mailbox,
+ t->time,
+ action_id,
+ l->action_data[action_id]);
+
+ TRACE("[Thread %2u] learner %u learn %s\n",
+ p->thread_id,
+ learner_id,
+ status ? "ok" : "error");
+
+ stats->n_pkts_learn[status] += 1;
+
+ /* Thread. */
+ thread_ip_inc(p);
+}
+
+/*
+ * forget.
+ */
+static int
+instr_forget_translate(struct rte_swx_pipeline *p __rte_unused,
+ struct action *action,
+ char **tokens __rte_unused,
+ int n_tokens,
+ struct instruction *instr,
+ struct instruction_data *data __rte_unused)
+{
+ CHECK(action, EINVAL);
+ CHECK(n_tokens == 1, EINVAL);
+
+ instr->type = INSTR_LEARNER_FORGET;
+
+ return 0;
+}
+
+static inline void
+instr_forget_exec(struct rte_swx_pipeline *p)
+{
+ struct thread *t = &p->threads[p->thread_id];
+ uint32_t learner_id = t->learner_id;
+ struct rte_swx_table_state *ts = &t->table_state[p->n_tables +
+ p->n_selectors + learner_id];
+ struct learner_runtime *l = &t->learners[learner_id];
+ struct learner_statistics *stats = &p->learner_stats[learner_id];
+
+ /* Table. */
+ rte_swx_table_learner_delete(ts->obj, l->mailbox);
+
+ TRACE("[Thread %2u] learner %u forget\n",
+ p->thread_id,
+ learner_id);
+
+ stats->n_pkts_forget += 1;
+
+ /* Thread. */
+ thread_ip_inc(p);
+}
+
/*
* extern.
*/
/*
* jmp.
*/
-static struct action *
-action_find(struct rte_swx_pipeline *p, const char *name);
-
static int
instr_jmp_translate(struct rte_swx_pipeline *p __rte_unused,
struct action *action __rte_unused,
instr,
data);
+ if (!strcmp(tokens[tpos], "learn"))
+ return instr_learn_translate(p,
+ action,
+ &tokens[tpos],
+ n_tokens - tpos,
+ instr,
+ data);
+
+ if (!strcmp(tokens[tpos], "forget"))
+ return instr_forget_translate(p,
+ action,
+ &tokens[tpos],
+ n_tokens - tpos,
+ instr,
+ data);
+
if (!strcmp(tokens[tpos], "extern"))
return instr_extern_translate(p,
action,
[INSTR_TABLE] = instr_table_exec,
[INSTR_SELECTOR] = instr_selector_exec,
+ [INSTR_LEARNER] = instr_learner_exec,
+ [INSTR_LEARNER_LEARN] = instr_learn_exec,
+ [INSTR_LEARNER_FORGET] = instr_forget_exec,
[INSTR_EXTERN_OBJ] = instr_extern_obj_exec,
[INSTR_EXTERN_FUNC] = instr_extern_func_exec,
return action_field_find(action, &name[2]);
}
+static int
+action_has_nbo_args(struct action *a)
+{
+ uint32_t i;
+
+ /* Return if the action does not have any args. */
+ if (!a->st)
+ return 0; /* FALSE */
+
+ for (i = 0; i < a->st->n_fields; i++)
+ if (a->args_endianness[i])
+ return 1; /* TRUE */
+
+ return 0; /* FALSE */
+}
+
+static int
+action_does_learning(struct action *a)
+{
+ uint32_t i;
+
+ for (i = 0; i < a->n_instructions; i++)
+ switch (a->instructions[i].type) {
+ case INSTR_LEARNER_LEARN:
+ return 1; /* TRUE */
+
+ case INSTR_LEARNER_FORGET:
+ return 1; /* TRUE */
+
+ default:
+ continue;
+ }
+
+ return 0; /* FALSE */
+}
+
int
rte_swx_pipeline_action_config(struct rte_swx_pipeline *p,
const char *name,
CHECK_NAME(name, EINVAL);
CHECK(!table_find(p, name), EEXIST);
CHECK(!selector_find(p, name), EEXIST);
+ CHECK(!learner_find(p, name), EEXIST);
CHECK(params, EINVAL);
a = action_find(p, action_name);
CHECK(a, EINVAL);
+ CHECK(!action_does_learning(a), EINVAL);
action_data_size = a->st ? a->st->n_bits / 8 : 0;
if (action_data_size > action_data_size_max)
CHECK_NAME(name, EINVAL);
CHECK(!table_find(p, name), EEXIST);
CHECK(!selector_find(p, name), EEXIST);
+ CHECK(!learner_find(p, name), EEXIST);
CHECK(params, EINVAL);
}
/*
- * Table state.
+ * Learner table.
*/
-static int
-table_state_build(struct rte_swx_pipeline *p)
+static struct learner *
+learner_find(struct rte_swx_pipeline *p, const char *name)
{
- struct table *table;
- struct selector *s;
-
- p->table_state = calloc(p->n_tables + p->n_selectors,
- sizeof(struct rte_swx_table_state));
- CHECK(p->table_state, ENOMEM);
+ struct learner *l;
- TAILQ_FOREACH(table, &p->tables, node) {
- struct rte_swx_table_state *ts = &p->table_state[table->id];
+ TAILQ_FOREACH(l, &p->learners, node)
+ if (!strcmp(l->name, name))
+ return l;
- if (table->type) {
- struct rte_swx_table_params *params;
+ return NULL;
+}
- /* ts->obj. */
- params = table_params_get(table);
- CHECK(params, ENOMEM);
+static struct learner *
+learner_find_by_id(struct rte_swx_pipeline *p, uint32_t id)
+{
+ struct learner *l = NULL;
- ts->obj = table->type->ops.create(params,
- NULL,
- table->args,
- p->numa_node);
+ TAILQ_FOREACH(l, &p->learners, node)
+ if (l->id == id)
+ return l;
- table_params_free(params);
- CHECK(ts->obj, ENODEV);
- }
+ return NULL;
+}
- /* ts->default_action_data. */
- if (table->action_data_size_max) {
- ts->default_action_data =
- malloc(table->action_data_size_max);
- CHECK(ts->default_action_data, ENOMEM);
+static int
+learner_match_fields_check(struct rte_swx_pipeline *p,
+ struct rte_swx_pipeline_learner_params *params,
+ struct header **header)
+{
+ struct header *h0 = NULL;
+ struct field *hf, *mf;
+ uint32_t i;
- memcpy(ts->default_action_data,
- table->default_action_data,
- table->action_data_size_max);
- }
+ /* Return if no match fields. */
+ if (!params->n_fields || !params->field_names)
+ return -EINVAL;
- /* ts->default_action_id. */
- ts->default_action_id = table->default_action->id;
- }
+ /* Check that all the match fields either belong to the same header
+ * or are all meta-data fields.
+ */
+ hf = header_field_parse(p, params->field_names[0], &h0);
+ mf = metadata_field_parse(p, params->field_names[0]);
+ if (!hf && !mf)
+ return -EINVAL;
- TAILQ_FOREACH(s, &p->selectors, node) {
- struct rte_swx_table_state *ts = &p->table_state[p->n_tables + s->id];
- struct rte_swx_table_selector_params *params;
+ for (i = 1; i < params->n_fields; i++)
+ if (h0) {
+ struct header *h;
- /* ts->obj. */
- params = selector_table_params_get(s);
- CHECK(params, ENOMEM);
+ hf = header_field_parse(p, params->field_names[i], &h);
+ if (!hf || (h->id != h0->id))
+ return -EINVAL;
+ } else {
+ mf = metadata_field_parse(p, params->field_names[i]);
+ if (!mf)
+ return -EINVAL;
+ }
- ts->obj = rte_swx_table_selector_create(params, NULL, p->numa_node);
+ /* Check that there are no duplicated match fields. */
+ for (i = 0; i < params->n_fields; i++) {
+ const char *field_name = params->field_names[i];
+ uint32_t j;
- selector_params_free(params);
- CHECK(ts->obj, ENODEV);
+ for (j = i + 1; j < params->n_fields; j++)
+ if (!strcmp(params->field_names[j], field_name))
+ return -EINVAL;
}
+ /* Return. */
+ if (header)
+ *header = h0;
+
return 0;
}
-static void
-table_state_build_free(struct rte_swx_pipeline *p)
+static int
+learner_action_args_check(struct rte_swx_pipeline *p, struct action *a, const char *mf_name)
{
- uint32_t i;
+ struct struct_type *mst = p->metadata_st, *ast = a->st;
+ struct field *mf, *af;
+ uint32_t mf_pos, i;
+
+ if (!ast) {
+ if (mf_name)
+ return -EINVAL;
+
+ return 0;
+ }
+
+ /* Check that mf_name is the name of a valid meta-data field. */
+ CHECK_NAME(mf_name, EINVAL);
+ mf = metadata_field_parse(p, mf_name);
+ CHECK(mf, EINVAL);
+
+ /* Check that there are enough meta-data fields, starting with the mf_name field, to cover
+ * all the action arguments.
+ */
+ mf_pos = mf - mst->fields;
+ CHECK(mst->n_fields - mf_pos >= ast->n_fields, EINVAL);
+
+ /* Check that the size of each of the identified meta-data fields matches exactly the size
+ * of the corresponding action argument.
+ */
+ for (i = 0; i < ast->n_fields; i++) {
+ mf = &mst->fields[mf_pos + i];
+ af = &ast->fields[i];
+
+ CHECK(mf->n_bits == af->n_bits, EINVAL);
+ }
+
+ return 0;
+}
+
+static int
+learner_action_learning_check(struct rte_swx_pipeline *p,
+ struct action *action,
+ const char **action_names,
+ uint32_t n_actions)
+{
+ uint32_t i;
+
+ /* For each "learn" instruction of the current action, check that the learned action (i.e.
+ * the action passed as argument to the "learn" instruction) is also enabled for the
+ * current learner table.
+ */
+ for (i = 0; i < action->n_instructions; i++) {
+ struct instruction *instr = &action->instructions[i];
+ uint32_t found = 0, j;
+
+ if (instr->type != INSTR_LEARNER_LEARN)
+ continue;
+
+ for (j = 0; j < n_actions; j++) {
+ struct action *a;
+
+ a = action_find(p, action_names[j]);
+ if (!a)
+ return -EINVAL;
+
+ if (a->id == instr->learn.action_id)
+ found = 1;
+ }
+
+ if (!found)
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int
+rte_swx_pipeline_learner_config(struct rte_swx_pipeline *p,
+ const char *name,
+ struct rte_swx_pipeline_learner_params *params,
+ uint32_t size,
+ uint32_t timeout)
+{
+ struct learner *l = NULL;
+ struct action *default_action;
+ struct header *header = NULL;
+ uint32_t action_data_size_max = 0, i;
+ int status = 0;
+
+ CHECK(p, EINVAL);
+
+ CHECK_NAME(name, EINVAL);
+ CHECK(!table_find(p, name), EEXIST);
+ CHECK(!selector_find(p, name), EEXIST);
+ CHECK(!learner_find(p, name), EEXIST);
+
+ CHECK(params, EINVAL);
+
+ /* Match checks. */
+ status = learner_match_fields_check(p, params, &header);
+ if (status)
+ return status;
+
+ /* Action checks. */
+ CHECK(params->n_actions, EINVAL);
+
+ CHECK(params->action_names, EINVAL);
+ for (i = 0; i < params->n_actions; i++) {
+ const char *action_name = params->action_names[i];
+ const char *action_field_name = params->action_field_names[i];
+ struct action *a;
+ uint32_t action_data_size;
+
+ CHECK_NAME(action_name, EINVAL);
+
+ a = action_find(p, action_name);
+ CHECK(a, EINVAL);
+
+ status = learner_action_args_check(p, a, action_field_name);
+ if (status)
+ return status;
+
+ status = learner_action_learning_check(p,
+ a,
+ params->action_names,
+ params->n_actions);
+ if (status)
+ return status;
+
+ action_data_size = a->st ? a->st->n_bits / 8 : 0;
+ if (action_data_size > action_data_size_max)
+ action_data_size_max = action_data_size;
+ }
+
+ CHECK_NAME(params->default_action_name, EINVAL);
+ for (i = 0; i < p->n_actions; i++)
+ if (!strcmp(params->action_names[i],
+ params->default_action_name))
+ break;
+ CHECK(i < params->n_actions, EINVAL);
+
+ default_action = action_find(p, params->default_action_name);
+ CHECK((default_action->st && params->default_action_data) ||
+ !params->default_action_data, EINVAL);
+
+ /* Any other checks. */
+ CHECK(size, EINVAL);
+ CHECK(timeout, EINVAL);
+
+ /* Memory allocation. */
+ l = calloc(1, sizeof(struct learner));
+ if (!l)
+ goto nomem;
+
+ l->fields = calloc(params->n_fields, sizeof(struct field *));
+ if (!l->fields)
+ goto nomem;
+
+ l->actions = calloc(params->n_actions, sizeof(struct action *));
+ if (!l->actions)
+ goto nomem;
+
+ l->action_arg = calloc(params->n_actions, sizeof(struct field *));
+ if (!l->action_arg)
+ goto nomem;
+
+ if (action_data_size_max) {
+ l->default_action_data = calloc(1, action_data_size_max);
+ if (!l->default_action_data)
+ goto nomem;
+ }
+
+ /* Node initialization. */
+ strcpy(l->name, name);
+
+ for (i = 0; i < params->n_fields; i++) {
+ const char *field_name = params->field_names[i];
+
+ l->fields[i] = header ?
+ header_field_parse(p, field_name, NULL) :
+ metadata_field_parse(p, field_name);
+ }
+
+ l->n_fields = params->n_fields;
+
+ l->header = header;
+
+ for (i = 0; i < params->n_actions; i++) {
+ const char *mf_name = params->action_field_names[i];
+
+ l->actions[i] = action_find(p, params->action_names[i]);
+
+ l->action_arg[i] = mf_name ? metadata_field_parse(p, mf_name) : NULL;
+ }
+
+ l->default_action = default_action;
+
+ if (default_action->st)
+ memcpy(l->default_action_data,
+ params->default_action_data,
+ default_action->st->n_bits / 8);
+
+ l->n_actions = params->n_actions;
+
+ l->default_action_is_const = params->default_action_is_const;
+
+ l->action_data_size_max = action_data_size_max;
+
+ l->size = size;
+
+ l->timeout = timeout;
+
+ l->id = p->n_learners;
+
+ /* Node add to tailq. */
+ TAILQ_INSERT_TAIL(&p->learners, l, node);
+ p->n_learners++;
+
+ return 0;
+
+nomem:
+ if (!l)
+ return -ENOMEM;
+
+ free(l->action_arg);
+ free(l->actions);
+ free(l->fields);
+ free(l);
+
+ return -ENOMEM;
+}
+
+static void
+learner_params_free(struct rte_swx_table_learner_params *params)
+{
+ if (!params)
+ return;
+
+ free(params->key_mask0);
+
+ free(params);
+}
+
+static struct rte_swx_table_learner_params *
+learner_params_get(struct learner *l)
+{
+ struct rte_swx_table_learner_params *params = NULL;
+ struct field *first, *last;
+ uint32_t i;
+
+ /* Memory allocation. */
+ params = calloc(1, sizeof(struct rte_swx_table_learner_params));
+ if (!params)
+ goto error;
+
+ /* Find first (smallest offset) and last (biggest offset) match fields. */
+ first = l->fields[0];
+ last = l->fields[0];
+
+ for (i = 0; i < l->n_fields; i++) {
+ struct field *f = l->fields[i];
+
+ if (f->offset < first->offset)
+ first = f;
+
+ if (f->offset > last->offset)
+ last = f;
+ }
+
+ /* Key offset and size. */
+ params->key_offset = first->offset / 8;
+ params->key_size = (last->offset + last->n_bits - first->offset) / 8;
+
+ /* Memory allocation. */
+ params->key_mask0 = calloc(1, params->key_size);
+ if (!params->key_mask0)
+ goto error;
+
+ /* Key mask. */
+ for (i = 0; i < l->n_fields; i++) {
+ struct field *f = l->fields[i];
+ uint32_t start = (f->offset - first->offset) / 8;
+ size_t size = f->n_bits / 8;
+
+ memset(¶ms->key_mask0[start], 0xFF, size);
+ }
+
+ /* Action data size. */
+ params->action_data_size = l->action_data_size_max;
+
+ /* Maximum number of keys. */
+ params->n_keys_max = l->size;
+
+ /* Timeout. */
+ params->key_timeout = l->timeout;
+
+ return params;
+
+error:
+ learner_params_free(params);
+ return NULL;
+}
+
+static void
+learner_build_free(struct rte_swx_pipeline *p)
+{
+ uint32_t i;
+
+ for (i = 0; i < RTE_SWX_PIPELINE_THREADS_MAX; i++) {
+ struct thread *t = &p->threads[i];
+ uint32_t j;
+
+ if (!t->learners)
+ continue;
+
+ for (j = 0; j < p->n_learners; j++) {
+ struct learner_runtime *r = &t->learners[j];
+
+ free(r->mailbox);
+ free(r->action_data);
+ }
+
+ free(t->learners);
+ t->learners = NULL;
+ }
+
+ if (p->learner_stats) {
+ for (i = 0; i < p->n_learners; i++)
+ free(p->learner_stats[i].n_pkts_action);
+
+ free(p->learner_stats);
+ }
+}
+
+static int
+learner_build(struct rte_swx_pipeline *p)
+{
+ uint32_t i;
+ int status = 0;
+
+ /* Per pipeline: learner statistics. */
+ p->learner_stats = calloc(p->n_learners, sizeof(struct learner_statistics));
+ CHECK(p->learner_stats, ENOMEM);
+
+ for (i = 0; i < p->n_learners; i++) {
+ p->learner_stats[i].n_pkts_action = calloc(p->n_actions, sizeof(uint64_t));
+ CHECK(p->learner_stats[i].n_pkts_action, ENOMEM);
+ }
+
+ /* Per thread: learner run-time. */
+ for (i = 0; i < RTE_SWX_PIPELINE_THREADS_MAX; i++) {
+ struct thread *t = &p->threads[i];
+ struct learner *l;
+
+ t->learners = calloc(p->n_learners, sizeof(struct learner_runtime));
+ if (!t->learners) {
+ status = -ENOMEM;
+ goto error;
+ }
+
+ TAILQ_FOREACH(l, &p->learners, node) {
+ struct learner_runtime *r = &t->learners[l->id];
+ uint64_t size;
+ uint32_t j;
+
+ /* r->mailbox. */
+ size = rte_swx_table_learner_mailbox_size_get();
+ if (size) {
+ r->mailbox = calloc(1, size);
+ if (!r->mailbox) {
+ status = -ENOMEM;
+ goto error;
+ }
+ }
+
+ /* r->key. */
+ r->key = l->header ?
+ &t->structs[l->header->struct_id] :
+ &t->structs[p->metadata_struct_id];
+
+ /* r->action_data. */
+ r->action_data = calloc(p->n_actions, sizeof(uint8_t *));
+ if (!r->action_data) {
+ status = -ENOMEM;
+ goto error;
+ }
+
+ for (j = 0; j < l->n_actions; j++) {
+ struct action *a = l->actions[j];
+ struct field *mf = l->action_arg[j];
+ uint8_t *m = t->structs[p->metadata_struct_id];
+
+ r->action_data[a->id] = mf ? &m[mf->offset / 8] : NULL;
+ }
+ }
+ }
+
+ return 0;
+
+error:
+ learner_build_free(p);
+ return status;
+}
+
+static void
+learner_free(struct rte_swx_pipeline *p)
+{
+ learner_build_free(p);
+
+ /* Learner tables. */
+ for ( ; ; ) {
+ struct learner *l;
+
+ l = TAILQ_FIRST(&p->learners);
+ if (!l)
+ break;
+
+ TAILQ_REMOVE(&p->learners, l, node);
+ free(l->fields);
+ free(l->actions);
+ free(l->action_arg);
+ free(l->default_action_data);
+ free(l);
+ }
+}
+
+/*
+ * Table state.
+ */
+static int
+table_state_build(struct rte_swx_pipeline *p)
+{
+ struct table *table;
+ struct selector *s;
+ struct learner *l;
+
+ p->table_state = calloc(p->n_tables + p->n_selectors,
+ sizeof(struct rte_swx_table_state));
+ CHECK(p->table_state, ENOMEM);
+
+ TAILQ_FOREACH(table, &p->tables, node) {
+ struct rte_swx_table_state *ts = &p->table_state[table->id];
+
+ if (table->type) {
+ struct rte_swx_table_params *params;
+
+ /* ts->obj. */
+ params = table_params_get(table);
+ CHECK(params, ENOMEM);
+
+ ts->obj = table->type->ops.create(params,
+ NULL,
+ table->args,
+ p->numa_node);
+
+ table_params_free(params);
+ CHECK(ts->obj, ENODEV);
+ }
+
+ /* ts->default_action_data. */
+ if (table->action_data_size_max) {
+ ts->default_action_data =
+ malloc(table->action_data_size_max);
+ CHECK(ts->default_action_data, ENOMEM);
+
+ memcpy(ts->default_action_data,
+ table->default_action_data,
+ table->action_data_size_max);
+ }
+
+ /* ts->default_action_id. */
+ ts->default_action_id = table->default_action->id;
+ }
+
+ TAILQ_FOREACH(s, &p->selectors, node) {
+ struct rte_swx_table_state *ts = &p->table_state[p->n_tables + s->id];
+ struct rte_swx_table_selector_params *params;
+
+ /* ts->obj. */
+ params = selector_table_params_get(s);
+ CHECK(params, ENOMEM);
+
+ ts->obj = rte_swx_table_selector_create(params, NULL, p->numa_node);
+
+ selector_params_free(params);
+ CHECK(ts->obj, ENODEV);
+ }
+
+ TAILQ_FOREACH(l, &p->learners, node) {
+ struct rte_swx_table_state *ts = &p->table_state[p->n_tables +
+ p->n_selectors + l->id];
+ struct rte_swx_table_learner_params *params;
+
+ /* ts->obj. */
+ params = learner_params_get(l);
+ CHECK(params, ENOMEM);
+
+ ts->obj = rte_swx_table_learner_create(params, p->numa_node);
+ learner_params_free(params);
+ CHECK(ts->obj, ENODEV);
+
+ /* ts->default_action_data. */
+ if (l->action_data_size_max) {
+ ts->default_action_data = malloc(l->action_data_size_max);
+ CHECK(ts->default_action_data, ENOMEM);
+
+ memcpy(ts->default_action_data,
+ l->default_action_data,
+ l->action_data_size_max);
+ }
+
+ /* ts->default_action_id. */
+ ts->default_action_id = l->default_action->id;
+ }
+
+ return 0;
+}
+
+static void
+table_state_build_free(struct rte_swx_pipeline *p)
+{
+ uint32_t i;
if (!p->table_state)
return;
rte_swx_table_selector_free(ts->obj);
}
+ for (i = 0; i < p->n_learners; i++) {
+ struct rte_swx_table_state *ts = &p->table_state[p->n_tables + p->n_selectors + i];
+
+ /* ts->obj. */
+ if (ts->obj)
+ rte_swx_table_learner_free(ts->obj);
+
+ /* ts->default_action_data. */
+ free(ts->default_action_data);
+ }
+
free(p->table_state);
p->table_state = NULL;
}
TAILQ_INIT(&pipeline->table_types);
TAILQ_INIT(&pipeline->tables);
TAILQ_INIT(&pipeline->selectors);
+ TAILQ_INIT(&pipeline->learners);
TAILQ_INIT(&pipeline->regarrays);
TAILQ_INIT(&pipeline->meter_profiles);
TAILQ_INIT(&pipeline->metarrays);
metarray_free(p);
regarray_free(p);
table_state_free(p);
+ learner_free(p);
selector_free(p);
table_free(p);
action_free(p);
if (status)
goto error;
+ status = learner_build(p);
+ if (status)
+ goto error;
+
status = table_state_build(p);
if (status)
goto error;
metarray_build_free(p);
regarray_build_free(p);
table_state_build_free(p);
+ learner_build_free(p);
selector_build_free(p);
table_build_free(p);
action_build_free(p);
pipeline->n_actions = n_actions;
pipeline->n_tables = n_tables;
pipeline->n_selectors = p->n_selectors;
+ pipeline->n_learners = p->n_learners;
pipeline->n_regarrays = p->n_regarrays;
pipeline->n_metarrays = p->n_metarrays;
return 0;
}
+int
+rte_swx_ctl_learner_info_get(struct rte_swx_pipeline *p,
+ uint32_t learner_id,
+ struct rte_swx_ctl_learner_info *learner)
+{
+ struct learner *l = NULL;
+
+ if (!p || !learner)
+ return -EINVAL;
+
+ l = learner_find_by_id(p, learner_id);
+ if (!l)
+ return -EINVAL;
+
+ strcpy(learner->name, l->name);
+
+ learner->n_match_fields = l->n_fields;
+ learner->n_actions = l->n_actions;
+ learner->default_action_is_const = l->default_action_is_const;
+ learner->size = l->size;
+
+ return 0;
+}
+
+int
+rte_swx_ctl_learner_match_field_info_get(struct rte_swx_pipeline *p,
+ uint32_t learner_id,
+ uint32_t match_field_id,
+ struct rte_swx_ctl_table_match_field_info *match_field)
+{
+ struct learner *l;
+ struct field *f;
+
+ if (!p || (learner_id >= p->n_learners) || !match_field)
+ return -EINVAL;
+
+ l = learner_find_by_id(p, learner_id);
+ if (!l || (match_field_id >= l->n_fields))
+ return -EINVAL;
+
+ f = l->fields[match_field_id];
+ match_field->match_type = RTE_SWX_TABLE_MATCH_EXACT;
+ match_field->is_header = l->header ? 1 : 0;
+ match_field->n_bits = f->n_bits;
+ match_field->offset = f->offset;
+
+ return 0;
+}
+
+int
+rte_swx_ctl_learner_action_info_get(struct rte_swx_pipeline *p,
+ uint32_t learner_id,
+ uint32_t learner_action_id,
+ struct rte_swx_ctl_table_action_info *learner_action)
+{
+ struct learner *l;
+
+ if (!p || (learner_id >= p->n_learners) || !learner_action)
+ return -EINVAL;
+
+ l = learner_find_by_id(p, learner_id);
+ if (!l || (learner_action_id >= l->n_actions))
+ return -EINVAL;
+
+ learner_action->action_id = l->actions[learner_action_id]->id;
+
+ return 0;
+}
+
int
rte_swx_pipeline_table_state_get(struct rte_swx_pipeline *p,
struct rte_swx_table_state **table_state)
return 0;
}
+int
+rte_swx_ctl_pipeline_learner_stats_read(struct rte_swx_pipeline *p,
+ const char *learner_name,
+ struct rte_swx_learner_stats *stats)
+{
+ struct learner *l;
+ struct learner_statistics *learner_stats;
+
+ if (!p || !learner_name || !learner_name[0] || !stats || !stats->n_pkts_action)
+ return -EINVAL;
+
+ l = learner_find(p, learner_name);
+ if (!l)
+ return -EINVAL;
+
+ learner_stats = &p->learner_stats[l->id];
+
+ memcpy(stats->n_pkts_action,
+ learner_stats->n_pkts_action,
+ p->n_actions * sizeof(uint64_t));
+
+ stats->n_pkts_hit = learner_stats->n_pkts_hit[1];
+ stats->n_pkts_miss = learner_stats->n_pkts_hit[0];
+
+ stats->n_pkts_learn_ok = learner_stats->n_pkts_learn[0];
+ stats->n_pkts_learn_err = learner_stats->n_pkts_learn[1];
+
+ stats->n_pkts_forget = learner_stats->n_pkts_forget;
+
+ return 0;
+}
+
int
rte_swx_ctl_regarray_info_get(struct rte_swx_pipeline *p,
uint32_t regarray_id,
const char *name,
struct rte_swx_pipeline_selector_params *params);
+/** Pipeline learner table parameters. */
+struct rte_swx_pipeline_learner_params {
+ /** The set of match fields for the current table.
+ * Restriction: All the match fields of the current table need to be
+ * part of the same struct, i.e. either all the match fields are part of
+ * the same header or all the match fields are part of the meta-data.
+ */
+ const char **field_names;
+
+ /** The number of match fields for the current table. Must be non-zero.
+ */
+ uint32_t n_fields;
+
+ /** The set of actions for the current table. */
+ const char **action_names;
+
+ /** The number of actions for the current table. Must be at least one.
+ */
+ uint32_t n_actions;
+
+ /** This table type allows adding the latest lookup key (typically done
+ * only in the case of lookup miss) to the table with a given action.
+ * The action arguments are picked up from the packet meta-data: for
+ * each action, a set of successive meta-data fields (with the name of
+ * the first such field provided here) is 1:1 mapped to the action
+ * arguments. These meta-data fields must be set with the actual values
+ * of the action arguments before the key add operation.
+ */
+ const char **action_field_names;
+
+ /** The default table action that gets executed on lookup miss. Must be
+ * one of the table actions included in the *action_names*.
+ */
+ const char *default_action_name;
+
+ /** Default action data. The size of this array is the action data size
+ * of the default action. Must be NULL if the default action data size
+ * is zero.
+ */
+ uint8_t *default_action_data;
+
+ /** If non-zero (true), then the default action of the current table
+ * cannot be changed. If zero (false), then the default action can be
+ * changed in the future with another action from the *action_names*
+ * list.
+ */
+ int default_action_is_const;
+};
+
+/**
+ * Pipeline learner table configure
+ *
+ * @param[out] p
+ * Pipeline handle.
+ * @param[in] name
+ * Learner table name.
+ * @param[in] params
+ * Learner table parameters.
+ * @param[in] size
+ * The maximum number of table entries. Must be non-zero.
+ * @param[in] timeout
+ * Table entry timeout in seconds. Must be non-zero.
+ * @return
+ * 0 on success or the following error codes otherwise:
+ * -EINVAL: Invalid argument;
+ * -ENOMEM: Not enough space/cannot allocate memory;
+ * -EEXIST: Learner table with this name already exists;
+ * -ENODEV: Learner table creation error.
+ */
+__rte_experimental
+int
+rte_swx_pipeline_learner_config(struct rte_swx_pipeline *p,
+ const char *name,
+ struct rte_swx_pipeline_learner_params *params,
+ uint32_t size,
+ uint32_t timeout);
+
/**
* Pipeline register array configure
*
#define TABLE_ACTIONS_BLOCK 4
#define SELECTOR_BLOCK 5
#define SELECTOR_SELECTOR_BLOCK 6
-#define APPLY_BLOCK 7
+#define LEARNER_BLOCK 7
+#define LEARNER_KEY_BLOCK 8
+#define LEARNER_ACTIONS_BLOCK 9
+#define APPLY_BLOCK 10
/*
* extobj.
return -EINVAL;
}
+/*
+ * learner.
+ *
+ * learner {
+ * key {
+ * MATCH_FIELD_NAME
+ * ...
+ * }
+ * actions {
+ * ACTION_NAME args METADATA_FIELD_NAME
+ * ...
+ * }
+ * default_action ACTION_NAME args none | ARGS_BYTE_ARRAY [ const ]
+ * size SIZE
+ * timeout TIMEOUT_IN_SECONDS
+ * }
+ */
+struct learner_spec {
+ char *name;
+ struct rte_swx_pipeline_learner_params params;
+ uint32_t size;
+ uint32_t timeout;
+};
+
+static void
+learner_spec_free(struct learner_spec *s)
+{
+ uintptr_t default_action_name;
+ uint32_t i;
+
+ if (!s)
+ return;
+
+ free(s->name);
+ s->name = NULL;
+
+ for (i = 0; i < s->params.n_fields; i++) {
+ uintptr_t name = (uintptr_t)s->params.field_names[i];
+
+ free((void *)name);
+ }
+
+ free(s->params.field_names);
+ s->params.field_names = NULL;
+
+ s->params.n_fields = 0;
+
+ for (i = 0; i < s->params.n_actions; i++) {
+ uintptr_t name = (uintptr_t)s->params.action_names[i];
+
+ free((void *)name);
+ }
+
+ free(s->params.action_names);
+ s->params.action_names = NULL;
+
+ for (i = 0; i < s->params.n_actions; i++) {
+ uintptr_t name = (uintptr_t)s->params.action_field_names[i];
+
+ free((void *)name);
+ }
+
+ free(s->params.action_field_names);
+ s->params.action_field_names = NULL;
+
+ s->params.n_actions = 0;
+
+ default_action_name = (uintptr_t)s->params.default_action_name;
+ free((void *)default_action_name);
+ s->params.default_action_name = NULL;
+
+ free(s->params.default_action_data);
+ s->params.default_action_data = NULL;
+
+ s->params.default_action_is_const = 0;
+
+ s->size = 0;
+
+ s->timeout = 0;
+}
+
+static int
+learner_key_statement_parse(uint32_t *block_mask,
+ char **tokens,
+ uint32_t n_tokens,
+ uint32_t n_lines,
+ uint32_t *err_line,
+ const char **err_msg)
+{
+ /* Check format. */
+ if ((n_tokens != 2) || strcmp(tokens[1], "{")) {
+ if (err_line)
+ *err_line = n_lines;
+ if (err_msg)
+ *err_msg = "Invalid key statement.";
+ return -EINVAL;
+ }
+
+ /* block_mask. */
+ *block_mask |= 1 << LEARNER_KEY_BLOCK;
+
+ return 0;
+}
+
+static int
+learner_key_block_parse(struct learner_spec *s,
+ uint32_t *block_mask,
+ char **tokens,
+ uint32_t n_tokens,
+ uint32_t n_lines,
+ uint32_t *err_line,
+ const char **err_msg)
+{
+ const char **new_field_names = NULL;
+ char *field_name = NULL;
+
+ /* Handle end of block. */
+ if ((n_tokens == 1) && !strcmp(tokens[0], "}")) {
+ *block_mask &= ~(1 << LEARNER_KEY_BLOCK);
+ return 0;
+ }
+
+ /* Check input arguments. */
+ if (n_tokens != 1) {
+ if (err_line)
+ *err_line = n_lines;
+ if (err_msg)
+ *err_msg = "Invalid match field statement.";
+ return -EINVAL;
+ }
+
+ field_name = strdup(tokens[0]);
+ new_field_names = realloc(s->params.field_names, (s->params.n_fields + 1) * sizeof(char *));
+ if (!field_name || !new_field_names) {
+ free(field_name);
+ free(new_field_names);
+
+ if (err_line)
+ *err_line = n_lines;
+ if (err_msg)
+ *err_msg = "Memory allocation failed.";
+ return -ENOMEM;
+ }
+
+ s->params.field_names = new_field_names;
+ s->params.field_names[s->params.n_fields] = field_name;
+ s->params.n_fields++;
+
+ return 0;
+}
+
+static int
+learner_actions_statement_parse(uint32_t *block_mask,
+ char **tokens,
+ uint32_t n_tokens,
+ uint32_t n_lines,
+ uint32_t *err_line,
+ const char **err_msg)
+{
+ /* Check format. */
+ if ((n_tokens != 2) || strcmp(tokens[1], "{")) {
+ if (err_line)
+ *err_line = n_lines;
+ if (err_msg)
+ *err_msg = "Invalid actions statement.";
+ return -EINVAL;
+ }
+
+ /* block_mask. */
+ *block_mask |= 1 << LEARNER_ACTIONS_BLOCK;
+
+ return 0;
+}
+
+static int
+learner_actions_block_parse(struct learner_spec *s,
+ uint32_t *block_mask,
+ char **tokens,
+ uint32_t n_tokens,
+ uint32_t n_lines,
+ uint32_t *err_line,
+ const char **err_msg)
+{
+ const char **new_action_names = NULL;
+ const char **new_action_field_names = NULL;
+ char *action_name = NULL, *action_field_name = NULL;
+ int has_args = 1;
+
+ /* Handle end of block. */
+ if ((n_tokens == 1) && !strcmp(tokens[0], "}")) {
+ *block_mask &= ~(1 << LEARNER_ACTIONS_BLOCK);
+ return 0;
+ }
+
+ /* Check input arguments. */
+ if ((n_tokens != 3) || strcmp(tokens[1], "args")) {
+ if (err_line)
+ *err_line = n_lines;
+ if (err_msg)
+ *err_msg = "Invalid action name statement.";
+ return -EINVAL;
+ }
+
+ if (!strcmp(tokens[2], "none"))
+ has_args = 0;
+
+ action_name = strdup(tokens[0]);
+
+ if (has_args)
+ action_field_name = strdup(tokens[2]);
+
+ new_action_names = realloc(s->params.action_names,
+ (s->params.n_actions + 1) * sizeof(char *));
+
+ new_action_field_names = realloc(s->params.action_field_names,
+ (s->params.n_actions + 1) * sizeof(char *));
+
+ if (!action_name ||
+ (has_args && !action_field_name) ||
+ !new_action_names ||
+ !new_action_field_names) {
+ free(action_name);
+ free(action_field_name);
+ free(new_action_names);
+ free(new_action_field_names);
+
+ if (err_line)
+ *err_line = n_lines;
+ if (err_msg)
+ *err_msg = "Memory allocation failed.";
+ return -ENOMEM;
+ }
+
+ s->params.action_names = new_action_names;
+ s->params.action_names[s->params.n_actions] = action_name;
+ s->params.action_field_names = new_action_field_names;
+ s->params.action_field_names[s->params.n_actions] = action_field_name;
+ s->params.n_actions++;
+
+ return 0;
+}
+
+static int
+learner_statement_parse(struct learner_spec *s,
+ uint32_t *block_mask,
+ char **tokens,
+ uint32_t n_tokens,
+ uint32_t n_lines,
+ uint32_t *err_line,
+ const char **err_msg)
+{
+ /* Check format. */
+ if ((n_tokens != 3) || strcmp(tokens[2], "{")) {
+ if (err_line)
+ *err_line = n_lines;
+ if (err_msg)
+ *err_msg = "Invalid learner statement.";
+ return -EINVAL;
+ }
+
+ /* spec. */
+ s->name = strdup(tokens[1]);
+ if (!s->name) {
+ if (err_line)
+ *err_line = n_lines;
+ if (err_msg)
+ *err_msg = "Memory allocation failed.";
+ return -ENOMEM;
+ }
+
+ /* block_mask. */
+ *block_mask |= 1 << LEARNER_BLOCK;
+
+ return 0;
+}
+
+static int
+learner_block_parse(struct learner_spec *s,
+ uint32_t *block_mask,
+ char **tokens,
+ uint32_t n_tokens,
+ uint32_t n_lines,
+ uint32_t *err_line,
+ const char **err_msg)
+{
+ if (*block_mask & (1 << LEARNER_KEY_BLOCK))
+ return learner_key_block_parse(s,
+ block_mask,
+ tokens,
+ n_tokens,
+ n_lines,
+ err_line,
+ err_msg);
+
+ if (*block_mask & (1 << LEARNER_ACTIONS_BLOCK))
+ return learner_actions_block_parse(s,
+ block_mask,
+ tokens,
+ n_tokens,
+ n_lines,
+ err_line,
+ err_msg);
+
+ /* Handle end of block. */
+ if ((n_tokens == 1) && !strcmp(tokens[0], "}")) {
+ *block_mask &= ~(1 << LEARNER_BLOCK);
+ return 0;
+ }
+
+ if (!strcmp(tokens[0], "key"))
+ return learner_key_statement_parse(block_mask,
+ tokens,
+ n_tokens,
+ n_lines,
+ err_line,
+ err_msg);
+
+ if (!strcmp(tokens[0], "actions"))
+ return learner_actions_statement_parse(block_mask,
+ tokens,
+ n_tokens,
+ n_lines,
+ err_line,
+ err_msg);
+
+ if (!strcmp(tokens[0], "default_action")) {
+ if (((n_tokens != 4) && (n_tokens != 5)) ||
+ strcmp(tokens[2], "args") ||
+ strcmp(tokens[3], "none") ||
+ ((n_tokens == 5) && strcmp(tokens[4], "const"))) {
+ if (err_line)
+ *err_line = n_lines;
+ if (err_msg)
+ *err_msg = "Invalid default_action statement.";
+ return -EINVAL;
+ }
+
+ if (s->params.default_action_name) {
+ if (err_line)
+ *err_line = n_lines;
+ if (err_msg)
+ *err_msg = "Duplicate default_action stmt.";
+ return -EINVAL;
+ }
+
+ s->params.default_action_name = strdup(tokens[1]);
+ if (!s->params.default_action_name) {
+ if (err_line)
+ *err_line = n_lines;
+ if (err_msg)
+ *err_msg = "Memory allocation failed.";
+ return -ENOMEM;
+ }
+
+ if (n_tokens == 5)
+ s->params.default_action_is_const = 1;
+
+ return 0;
+ }
+
+ if (!strcmp(tokens[0], "size")) {
+ char *p = tokens[1];
+
+ if (n_tokens != 2) {
+ if (err_line)
+ *err_line = n_lines;
+ if (err_msg)
+ *err_msg = "Invalid size statement.";
+ return -EINVAL;
+ }
+
+ s->size = strtoul(p, &p, 0);
+ if (p[0]) {
+ if (err_line)
+ *err_line = n_lines;
+ if (err_msg)
+ *err_msg = "Invalid size argument.";
+ return -EINVAL;
+ }
+
+ return 0;
+ }
+
+ if (!strcmp(tokens[0], "timeout")) {
+ char *p = tokens[1];
+
+ if (n_tokens != 2) {
+ if (err_line)
+ *err_line = n_lines;
+ if (err_msg)
+ *err_msg = "Invalid timeout statement.";
+ return -EINVAL;
+ }
+
+ s->timeout = strtoul(p, &p, 0);
+ if (p[0]) {
+ if (err_line)
+ *err_line = n_lines;
+ if (err_msg)
+ *err_msg = "Invalid timeout argument.";
+ return -EINVAL;
+ }
+
+ return 0;
+ }
+
+ /* Anything else. */
+ if (err_line)
+ *err_line = n_lines;
+ if (err_msg)
+ *err_msg = "Invalid statement.";
+ return -EINVAL;
+}
+
/*
* regarray.
*
struct action_spec action_spec = {0};
struct table_spec table_spec = {0};
struct selector_spec selector_spec = {0};
+ struct learner_spec learner_spec = {0};
struct regarray_spec regarray_spec = {0};
struct metarray_spec metarray_spec = {0};
struct apply_spec apply_spec = {0};
continue;
}
+ /* learner block. */
+ if (block_mask & (1 << LEARNER_BLOCK)) {
+ status = learner_block_parse(&learner_spec,
+ &block_mask,
+ tokens,
+ n_tokens,
+ n_lines,
+ err_line,
+ err_msg);
+ if (status)
+ goto error;
+
+ if (block_mask & (1 << LEARNER_BLOCK))
+ continue;
+
+ /* End of block. */
+ status = rte_swx_pipeline_learner_config(p,
+ learner_spec.name,
+ &learner_spec.params,
+ learner_spec.size,
+ learner_spec.timeout);
+ if (status) {
+ if (err_line)
+ *err_line = n_lines;
+ if (err_msg)
+ *err_msg = "Learner table configuration error.";
+ goto error;
+ }
+
+ learner_spec_free(&learner_spec);
+
+ continue;
+ }
+
/* apply block. */
if (block_mask & (1 << APPLY_BLOCK)) {
status = apply_block_parse(&apply_spec,
continue;
}
+ /* learner. */
+ if (!strcmp(tokens[0], "learner")) {
+ status = learner_statement_parse(&learner_spec,
+ &block_mask,
+ tokens,
+ n_tokens,
+ n_lines,
+ err_line,
+ err_msg);
+ if (status)
+ goto error;
+
+ continue;
+ }
+
/* regarray. */
if (!strcmp(tokens[0], "regarray")) {
status = regarray_statement_parse(®array_spec,
action_spec_free(&action_spec);
table_spec_free(&table_spec);
selector_spec_free(&selector_spec);
+ learner_spec_free(&learner_spec);
regarray_spec_free(®array_spec);
metarray_spec_free(&metarray_spec);
apply_spec_free(&apply_spec);
rte_swx_ctl_selector_field_info_get;
rte_swx_ctl_selector_group_id_field_info_get;
rte_swx_ctl_selector_member_id_field_info_get;
+
+ #added in 21.11
+ rte_swx_ctl_pipeline_learner_default_entry_add;
+ rte_swx_ctl_pipeline_learner_default_entry_read;
+ rte_swx_ctl_pipeline_learner_stats_read;
+ rte_swx_ctl_learner_action_info_get;
+ rte_swx_ctl_learner_info_get;
+ rte_swx_ctl_learner_match_field_info_get;
+ rte_swx_pipeline_learner_config;
};