From 4f59d3726147e7cf0ff98931b2512687727838ad Mon Sep 17 00:00:00 2001 From: Cristian Dumitrescu Date: Mon, 20 Sep 2021 16:01:31 +0100 Subject: [PATCH] pipeline: support learner tables Add pipeline level support for learner tables. Signed-off-by: Cristian Dumitrescu --- lib/pipeline/rte_swx_ctl.c | 479 +++++++++++- lib/pipeline/rte_swx_ctl.h | 186 +++++ lib/pipeline/rte_swx_pipeline.c | 1041 ++++++++++++++++++++++++-- lib/pipeline/rte_swx_pipeline.h | 77 ++ lib/pipeline/rte_swx_pipeline_spec.c | 470 +++++++++++- lib/pipeline/version.map | 9 + 6 files changed, 2207 insertions(+), 55 deletions(-) diff --git a/lib/pipeline/rte_swx_ctl.c b/lib/pipeline/rte_swx_ctl.c index dc093860de..86b58e21dc 100644 --- a/lib/pipeline/rte_swx_ctl.c +++ b/lib/pipeline/rte_swx_ctl.c @@ -123,12 +123,26 @@ struct selector { 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; @@ -924,6 +938,70 @@ selector_params_get(struct rte_swx_ctl_pipeline *ctl, uint32_t selector_id) 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) { @@ -954,6 +1032,14 @@ 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; } @@ -1020,6 +1106,29 @@ table_state_create(struct rte_swx_ctl_pipeline *ctl) } } + /* 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: @@ -1037,6 +1146,8 @@ rte_swx_ctl_pipeline_free(struct rte_swx_ctl_pipeline *ctl) table_state_free(ctl); + learner_free(ctl); + selector_free(ctl); table_free(ctl); @@ -1251,6 +1362,54 @@ rte_swx_ctl_pipeline_create(struct rte_swx_pipeline *p) 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) @@ -1685,9 +1844,8 @@ table_rollfwd1(struct rte_swx_ctl_pipeline *ctl, uint32_t table_id) 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; } @@ -2099,6 +2257,178 @@ selector_abort(struct rte_swx_ctl_pipeline *ctl, uint32_t selector_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) { @@ -2110,6 +2440,7 @@ 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); @@ -2123,9 +2454,15 @@ rte_swx_ctl_pipeline_commit(struct rte_swx_ctl_pipeline *ctl, int abort_on_fail) 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. */ @@ -2151,6 +2488,11 @@ rte_swx_ctl_pipeline_commit(struct rte_swx_ctl_pipeline *ctl, int abort_on_fail) 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: @@ -2166,6 +2508,10 @@ rollback: selector_abort(ctl, i); } + if (abort_on_fail) + for (i = 0; i < ctl->info.n_learners; i++) + learner_abort(ctl, i); + return status; } @@ -2182,6 +2528,9 @@ rte_swx_ctl_pipeline_abort(struct rte_swx_ctl_pipeline *ctl) 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 @@ -2460,6 +2809,130 @@ error: 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, diff --git a/lib/pipeline/rte_swx_ctl.h b/lib/pipeline/rte_swx_ctl.h index f37301cf95..807597229d 100644 --- a/lib/pipeline/rte_swx_ctl.h +++ b/lib/pipeline/rte_swx_ctl.h @@ -52,6 +52,9 @@ struct rte_swx_ctl_pipeline_info { /** Number of selector tables. */ uint32_t n_selectors; + /** Number of learner tables. */ + uint32_t n_learners; + /** Number of register arrays. */ uint32_t n_regarrays; @@ -512,6 +515,142 @@ rte_swx_ctl_pipeline_selector_stats_read(struct rte_swx_pipeline *p, 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. */ @@ -761,6 +900,27 @@ rte_swx_ctl_pipeline_selector_group_member_delete(struct rte_swx_ctl_pipeline *c 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 * @@ -819,6 +979,32 @@ rte_swx_ctl_pipeline_table_entry_read(struct rte_swx_ctl_pipeline *ctl, 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 * diff --git a/lib/pipeline/rte_swx_pipeline.c b/lib/pipeline/rte_swx_pipeline.c index 96786fb9a0..f89a134a52 100644 --- a/lib/pipeline/rte_swx_pipeline.c +++ b/lib/pipeline/rte_swx_pipeline.c @@ -16,6 +16,7 @@ #include #include +#include #include "rte_swx_pipeline.h" #include "rte_swx_ctl.h" @@ -511,6 +512,13 @@ enum instruction_type { /* 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, @@ -636,6 +644,10 @@ struct instr_table { uint8_t table_id; }; +struct instr_learn { + uint8_t action_id; +}; + struct instr_extern_obj { uint8_t ext_obj_id; uint8_t func_id; @@ -726,6 +738,7 @@ struct instruction { 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; @@ -746,7 +759,7 @@ struct action { 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; @@ -839,6 +852,47 @@ struct selector_statistics { 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. */ @@ -919,9 +973,12 @@ struct thread { /* 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; @@ -1355,6 +1412,7 @@ struct rte_swx_pipeline { 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; @@ -1365,6 +1423,7 @@ struct rte_swx_pipeline { 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; @@ -1378,6 +1437,7 @@ struct rte_swx_pipeline { 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; @@ -3625,6 +3685,9 @@ table_find(struct rte_swx_pipeline *p, const char *name); 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, @@ -3635,6 +3698,7 @@ instr_table_translate(struct rte_swx_pipeline *p, { struct table *t; struct selector *s; + struct learner *l; CHECK(!action, EINVAL); CHECK(n_tokens == 2, EINVAL); @@ -3653,6 +3717,13 @@ instr_table_translate(struct rte_swx_pipeline *p, 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); } @@ -3746,6 +3817,168 @@ instr_selector_exec(struct rte_swx_pipeline *p) 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. */ @@ -7159,9 +7392,6 @@ instr_meter_imi_exec(struct rte_swx_pipeline *p) /* * 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, @@ -8136,6 +8366,22 @@ instr_translate(struct rte_swx_pipeline *p, 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, @@ -9096,6 +9342,9 @@ static instr_exec_t instruction_table[] = { [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, @@ -9191,6 +9440,42 @@ action_field_parse(struct action *action, const char *name) 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, @@ -9546,6 +9831,7 @@ rte_swx_pipeline_table_config(struct rte_swx_pipeline *p, 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); @@ -9566,6 +9852,7 @@ rte_swx_pipeline_table_config(struct rte_swx_pipeline *p, 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) @@ -9964,6 +10251,7 @@ rte_swx_pipeline_selector_config(struct rte_swx_pipeline *p, 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); @@ -10221,73 +10509,604 @@ selector_free(struct rte_swx_pipeline *p) } /* - * 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; @@ -10312,6 +11131,17 @@ table_state_build_free(struct rte_swx_pipeline *p) 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; } @@ -10653,6 +11483,7 @@ rte_swx_pipeline_config(struct rte_swx_pipeline **p, int numa_node) 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); @@ -10675,6 +11506,7 @@ rte_swx_pipeline_free(struct rte_swx_pipeline *p) metarray_free(p); regarray_free(p); table_state_free(p); + learner_free(p); selector_free(p); table_free(p); action_free(p); @@ -10759,6 +11591,10 @@ rte_swx_pipeline_build(struct rte_swx_pipeline *p) if (status) goto error; + status = learner_build(p); + if (status) + goto error; + status = table_state_build(p); if (status) goto error; @@ -10778,6 +11614,7 @@ 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); @@ -10839,6 +11676,7 @@ rte_swx_ctl_pipeline_info_get(struct rte_swx_pipeline *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; @@ -11084,6 +11922,75 @@ rte_swx_ctl_selector_member_id_field_info_get(struct rte_swx_pipeline *p, 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) @@ -11188,6 +12095,38 @@ rte_swx_ctl_pipeline_selector_stats_read(struct rte_swx_pipeline *p, 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, diff --git a/lib/pipeline/rte_swx_pipeline.h b/lib/pipeline/rte_swx_pipeline.h index 5afca2bc20..2f18a820b9 100644 --- a/lib/pipeline/rte_swx_pipeline.h +++ b/lib/pipeline/rte_swx_pipeline.h @@ -676,6 +676,83 @@ rte_swx_pipeline_selector_config(struct rte_swx_pipeline *p, 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 * diff --git a/lib/pipeline/rte_swx_pipeline_spec.c b/lib/pipeline/rte_swx_pipeline_spec.c index c57893f18c..d9cd1d0595 100644 --- a/lib/pipeline/rte_swx_pipeline_spec.c +++ b/lib/pipeline/rte_swx_pipeline_spec.c @@ -20,7 +20,10 @@ #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. @@ -1281,6 +1284,420 @@ selector_block_parse(struct selector_spec *s, 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. * @@ -1545,6 +1962,7 @@ rte_swx_pipeline_build_from_spec(struct rte_swx_pipeline *p, 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}; @@ -1761,6 +2179,40 @@ rte_swx_pipeline_build_from_spec(struct rte_swx_pipeline *p, 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, @@ -1934,6 +2386,21 @@ rte_swx_pipeline_build_from_spec(struct rte_swx_pipeline *p, 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, @@ -2042,6 +2509,7 @@ error: 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); diff --git a/lib/pipeline/version.map b/lib/pipeline/version.map index 2b68f584a4..8bc90e7cd7 100644 --- a/lib/pipeline/version.map +++ b/lib/pipeline/version.map @@ -129,4 +129,13 @@ EXPERIMENTAL { 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; }; -- 2.20.1