DPDK patches and discussions
 help / color / mirror / Atom feed
* [PATCH 1/3] table: improve learner table timers
@ 2022-04-21 15:59 Cristian Dumitrescu
  2022-04-21 15:59 ` [PATCH 2/3] pipeline: " Cristian Dumitrescu
                   ` (2 more replies)
  0 siblings, 3 replies; 10+ messages in thread
From: Cristian Dumitrescu @ 2022-04-21 15:59 UTC (permalink / raw)
  To: dev

Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---
 lib/pipeline/rte_swx_pipeline.c          |   3 +-
 lib/pipeline/rte_swx_pipeline_internal.h |   3 +-
 lib/table/rte_swx_table_learner.c        | 109 ++++++++++++++++++++---
 lib/table/rte_swx_table_learner.h        |  90 +++++++++++++++++--
 lib/table/version.map                    |   4 +
 5 files changed, 189 insertions(+), 20 deletions(-)

diff --git a/lib/pipeline/rte_swx_pipeline.c b/lib/pipeline/rte_swx_pipeline.c
index dfbac929c7..17be31d5a4 100644
--- a/lib/pipeline/rte_swx_pipeline.c
+++ b/lib/pipeline/rte_swx_pipeline.c
@@ -8788,7 +8788,8 @@ learner_params_get(struct learner *l)
 	params->n_keys_max = l->size;
 
 	/* Timeout. */
-	params->key_timeout = l->timeout;
+	params->key_timeout[0] = l->timeout;
+	params->n_key_timeouts = 1;
 
 	return params;
 
diff --git a/lib/pipeline/rte_swx_pipeline_internal.h b/lib/pipeline/rte_swx_pipeline_internal.h
index 381a35c6e0..51bb464f5f 100644
--- a/lib/pipeline/rte_swx_pipeline_internal.h
+++ b/lib/pipeline/rte_swx_pipeline_internal.h
@@ -2215,7 +2215,8 @@ __instr_learn_exec(struct rte_swx_pipeline *p,
 					   l->mailbox,
 					   t->time,
 					   action_id,
-					   &t->metadata[mf_offset]);
+					   &t->metadata[mf_offset],
+					   0);
 
 	TRACE("[Thread %2u] learner %u learn %s\n",
 	      p->thread_id,
diff --git a/lib/table/rte_swx_table_learner.c b/lib/table/rte_swx_table_learner.c
index 15576c2aa3..02f7613c22 100644
--- a/lib/table/rte_swx_table_learner.c
+++ b/lib/table/rte_swx_table_learner.c
@@ -231,11 +231,14 @@ table_keycmp(void *a, void *b, void *b_mask, uint32_t n_bytes)
 #define TABLE_KEYS_PER_BUCKET 4
 
 #define TABLE_BUCKET_PAD_SIZE \
-	(RTE_CACHE_LINE_SIZE - TABLE_KEYS_PER_BUCKET * (sizeof(uint32_t) + sizeof(uint32_t)))
+	(RTE_CACHE_LINE_SIZE - TABLE_KEYS_PER_BUCKET * (sizeof(uint32_t) + \
+	                                                sizeof(uint32_t) + \
+	                                                sizeof(uint8_t)))
 
 struct table_bucket {
 	uint32_t time[TABLE_KEYS_PER_BUCKET];
 	uint32_t sig[TABLE_KEYS_PER_BUCKET];
+	uint8_t key_timeout_id[TABLE_KEYS_PER_BUCKET];
 	uint8_t pad[TABLE_BUCKET_PAD_SIZE];
 	uint8_t key[0];
 };
@@ -284,8 +287,11 @@ struct table_params {
 	/* log2(bucket_size). Purpose: avoid multiplication with non-power of 2 numbers. */
 	size_t bucket_size_log2;
 
-	/* Timeout in CPU clock cycles. */
-	uint64_t key_timeout;
+	/* Set of all possible key timeout values measured in CPU clock cycles. */
+	uint64_t key_timeout[RTE_SWX_TABLE_LEARNER_N_KEY_TIMEOUTS_MAX];
+
+	/* Number of key timeout values. */
+	uint32_t n_key_timeouts;
 
 	/* Total memory size. */
 	size_t total_size;
@@ -305,15 +311,23 @@ struct table {
 static int
 table_params_get(struct table_params *p, struct rte_swx_table_learner_params *params)
 {
+	uint32_t i;
+
 	/* Check input parameters. */
 	if (!params ||
 	    !params->key_size ||
 	    (params->key_size > 64) ||
 	    !params->n_keys_max ||
 	    (params->n_keys_max > 1U << 31) ||
-	    !params->key_timeout)
+	    !params->key_timeout ||
+	    !params->n_key_timeouts ||
+	    (params->n_key_timeouts > RTE_SWX_TABLE_LEARNER_N_KEY_TIMEOUTS_MAX))
 		return -EINVAL;
 
+	for (i = 0; i < params->n_key_timeouts; i++)
+		if (!params->key_timeout[i])
+			return -EINVAL;
+
 	/* Key. */
 	p->key_size = params->key_size;
 
@@ -346,7 +360,17 @@ table_params_get(struct table_params *p, struct rte_swx_table_learner_params *pa
 	p->bucket_size_log2 = __builtin_ctzll(p->bucket_size);
 
 	/* Timeout. */
-	p->key_timeout = params->key_timeout * rte_get_tsc_hz();
+	for (i = 0; i < params->n_key_timeouts; i++) {
+		p->key_timeout[i] = params->key_timeout[i] * rte_get_tsc_hz();
+
+		if (!(p->key_timeout[i] >> 32))
+			p->key_timeout[i] = 1LLU << 32;
+	}
+
+	p->n_key_timeouts = rte_align32pow2(params->n_key_timeouts);
+
+	for ( ; i < p->n_key_timeouts; i++)
+		p->key_timeout[i] = p->key_timeout[0];
 
 	/* Total size. */
 	p->total_size = sizeof(struct table) + p->n_buckets * p->bucket_size;
@@ -505,8 +529,6 @@ rte_swx_table_learner_lookup(void *table,
 				/* Hit. */
 				rte_prefetch0(data);
 
-				b->time[i] = (input_time + t->params.key_timeout) >> 32;
-
 				m->hit = 1;
 				m->bucket_key_pos = i;
 				m->state = 0;
@@ -536,23 +558,83 @@ rte_swx_table_learner_lookup(void *table,
 	}
 }
 
+void
+rte_swx_table_learner_rearm(void *table,
+			    void *mailbox,
+			    uint64_t input_time)
+{
+	struct table *t = table;
+	struct mailbox *m = mailbox;
+	struct table_bucket *b;
+	size_t bucket_key_pos;
+	uint64_t key_timeout;
+	uint32_t key_timeout_id;
+
+	if (!m->hit)
+		return;
+
+	b = m->bucket;
+	bucket_key_pos = m->bucket_key_pos;
+
+	key_timeout_id = b->key_timeout_id[bucket_key_pos];
+	key_timeout = t->params.key_timeout[key_timeout_id];
+	b->time[bucket_key_pos] = (input_time + key_timeout) >> 32;
+}
+
+void
+rte_swx_table_learner_rearm_new(void *table,
+				void *mailbox,
+				uint64_t input_time,
+				uint32_t key_timeout_id)
+{
+	struct table *t = table;
+	struct mailbox *m = mailbox;
+	struct table_bucket *b;
+	size_t bucket_key_pos;
+	uint64_t key_timeout;
+
+	if (!m->hit)
+		return;
+
+	b = m->bucket;
+	bucket_key_pos = m->bucket_key_pos;
+
+	key_timeout_id &= t->params.n_key_timeouts - 1;
+	key_timeout = t->params.key_timeout[key_timeout_id];
+	b->time[bucket_key_pos] = (input_time + key_timeout) >> 32;
+	b->key_timeout_id[bucket_key_pos] = (uint8_t)key_timeout_id;
+}
+
 uint32_t
 rte_swx_table_learner_add(void *table,
 			  void *mailbox,
 			  uint64_t input_time,
 			  uint64_t action_id,
-			  uint8_t *action_data)
+			  uint8_t *action_data,
+			  uint32_t key_timeout_id)
 {
 	struct table *t = table;
 	struct mailbox *m = mailbox;
 	struct table_bucket *b = m->bucket;
+	uint64_t key_timeout;
 	uint32_t i;
 
-	/* Lookup hit: The key, key signature and key time are already properly configured (the key
-	 * time was bumped by lookup), only the key data need to be updated.
+	/* Adjust the key timeout ID to fit the valid range. */
+	key_timeout_id &= t->params.n_key_timeouts - 1;
+	key_timeout = t->params.key_timeout[key_timeout_id];
+
+	/* Lookup hit: The following bucket fields need to be updated:
+	 * - key (key, sig): NO (already correctly set).
+	 * - key timeout (key_timeout_id, time): YES.
+	 * - key data (data): YES.
 	 */
 	if (m->hit) {
-		uint64_t *data = table_bucket_data_get(t, b, m->bucket_key_pos);
+		size_t bucket_key_pos = m->bucket_key_pos;
+		uint64_t *data = table_bucket_data_get(t, b, bucket_key_pos);
+
+		/* Install the key timeout. */
+		b->time[bucket_key_pos] = (input_time + key_timeout) >> 32;
+		b->key_timeout_id[bucket_key_pos] = (uint8_t)key_timeout_id;
 
 		/* Install the key data. */
 		data[0] = action_id;
@@ -576,9 +658,10 @@ rte_swx_table_learner_add(void *table,
 			uint8_t *key = table_bucket_key_get(t, b, i);
 			uint64_t *data = table_bucket_data_get(t, b, i);
 
-			/* Install the key. */
-			b->time[i] = (input_time + t->params.key_timeout) >> 32;
+			/* Install the key and the key timeout. */
+			b->time[i] = (input_time + key_timeout) >> 32;
 			b->sig[i] = m->input_sig;
+			b->key_timeout_id[i] = (uint8_t)key_timeout_id;
 			memcpy(key, m->input_key, t->params.key_size);
 
 			/* Install the key data. */
diff --git a/lib/table/rte_swx_table_learner.h b/lib/table/rte_swx_table_learner.h
index eb9d7689fd..8b3128ec5d 100644
--- a/lib/table/rte_swx_table_learner.h
+++ b/lib/table/rte_swx_table_learner.h
@@ -18,13 +18,43 @@ extern "C" {
  * implementation of the "add on miss" scenario: whenever the lookup key is not found in the table
  * (lookup miss), the data plane can decide to add this key to the table with a given action with no
  * control plane intervention. Likewise, the table keys expire based on a configurable timeout and
- * are automatically deleted from the table with no control plane intervention.
+ * are thus automatically removed from the table with no control plane intervention.
+ *
+ * The keys are not automatically rearmed on lookup hit. To delay the key expiration, the key timer
+ * has to be explicitly reinitialized on lookup hit. The key will be kept in the table as long as it
+ * is frequently hit and explicitly rearmed on every hit.
+ *
+ * Operation overview:
+ * 1) Lookup miss:
+ *      a) add: Add the current input key (the key that missed the lookup) to the table with given
+ *              action, action parameters and expiration timeout. This is the way to populate the
+ *              table (which is empty initially). Data plane operation.
+ *      b) Do nothing: Keep the current input key out of the table.
+ * 2) Lookup hit:
+ *      a) add: Update the action, action parameters and/or the expiration timeout for the current
+ *              input key, which is already in the table. The expiration timer of the key is
+ *              automatically rearmed. Data plane operation.
+ *      b) rearm: Rearm the expiration timer for the current input key, which is already in the
+ *              table. The timeout value used for the expiration timer is either the same as the one
+ *              currently associated with the key or a new one can be provided as input. Data plane
+ *              operation.
+ *      c) delete: Delete the current input key from the table. The purpose of this operation is to
+ *              force the deletion of the key from the table before the key expires on timeout due
+ *              to inactivity. Data plane operation.
+ *      d) Do nothing: Keep the expiration timer of the current input key running down. This key
+ *              will thus expire naturally, unless it is hit again as part of a subsequent lookup
+ *              operation, when the key timer can be rearmed or re-added to prolong its life.
  */
 
 #include <stdint.h>
 
 #include <rte_compat.h>
 
+/** Maximum number of key timeout values per learner table. */
+#ifndef RTE_SWX_TABLE_LEARNER_N_KEY_TIMEOUTS_MAX
+#define RTE_SWX_TABLE_LEARNER_N_KEY_TIMEOUTS_MAX 16
+#endif
+
 /** Learner table creation parameters. */
 struct rte_swx_table_learner_params {
 	/** Key size in bytes. Must be non-zero. */
@@ -50,10 +80,16 @@ struct rte_swx_table_learner_params {
 	/** Maximum number of keys to be stored in the table together with their associated data. */
 	uint32_t n_keys_max;
 
-	/** Key timeout in seconds. Must be non-zero. Each table key expires and is automatically
-	 * deleted from the table after this many seconds.
+	/** The set of all possible key timeout values measured in seconds. Each value must be
+	 * non-zero. Each table key expires and is automatically deleted from the table after
+	 * this many seconds.
 	 */
-	uint32_t key_timeout;
+	uint32_t *key_timeout;
+
+	/** Number of possible key timeout values present in the *key_timeout* set. It must be less
+	 * than or equal to *RTE_SWX_TABLE_LEARNER_N_KEY_TIMEOUTS_MAX*.
+	 */
+	uint32_t n_key_timeouts;
 };
 
 /**
@@ -160,6 +196,8 @@ rte_swx_table_learner_lookup(void *table,
  *   ID of the action associated with the key.
  * @param[out] action_data
  *   Action data for the *action_id* action.
+ * @param[in] key_timeout_id
+ *   Key timeout ID.
  * @return
  *   0 on success, 1 or error (table full).
  */
@@ -169,7 +207,49 @@ rte_swx_table_learner_add(void *table,
 			  void *mailbox,
 			  uint64_t time,
 			  uint64_t action_id,
-			  uint8_t *action_data);
+			  uint8_t *action_data,
+			  uint32_t key_timeout_id);
+
+/**
+ * Learner table key rearm with same timeout value
+ *
+ * This operation takes the latest key that was looked up in the table and, in case of lookup hit,
+ * it rearms its expiration timer using the same timeout value currently associated with the key.
+ *
+ * @param[in] table
+ *   Table handle.
+ * @param[in] mailbox
+ *   Mailbox for the current operation.
+ * @param[in] time
+ *   Current time measured in CPU clock cycles.
+ */
+__rte_experimental
+void
+rte_swx_table_learner_rearm(void *table,
+			    void *mailbox,
+			    uint64_t time);
+
+/**
+ * Learner table key rearm with given timeout value
+ *
+ * This operation takes the latest key that was looked up in the table and, in case of lookup hit,
+ * it rearms its expiration timer using the given timeout value.
+ *
+ * @param[in] table
+ *   Table handle.
+ * @param[in] mailbox
+ *   Mailbox for the current operation.
+ * @param[in] time
+ *   Current time measured in CPU clock cycles.
+ * @param[in] key_timeout_id
+ *   Key timeout ID.
+ */
+__rte_experimental
+void
+rte_swx_table_learner_rearm_new(void *table,
+				void *mailbox,
+				uint64_t time,
+				uint32_t key_timeout_id);
 
 /**
  * Learner table key delete
diff --git a/lib/table/version.map b/lib/table/version.map
index efe5f6e52c..d27d649332 100644
--- a/lib/table/version.map
+++ b/lib/table/version.map
@@ -45,4 +45,8 @@ EXPERIMENTAL {
 	rte_swx_table_learner_free;
 	rte_swx_table_learner_lookup;
 	rte_swx_table_learner_mailbox_size_get;
+
+	#added in 22.07
+	rte_swx_table_learner_rearm;
+	rte_swx_table_learner_rearm_new;
 };
-- 
2.17.1


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH 2/3] pipeline: improve learner table timers
  2022-04-21 15:59 [PATCH 1/3] table: improve learner table timers Cristian Dumitrescu
@ 2022-04-21 15:59 ` Cristian Dumitrescu
  2022-04-21 15:59 ` [PATCH 3/3] examples/pipeline: " Cristian Dumitrescu
  2022-04-22 13:03 ` [PATCH V2 1/3] table: " Cristian Dumitrescu
  2 siblings, 0 replies; 10+ messages in thread
From: Cristian Dumitrescu @ 2022-04-21 15:59 UTC (permalink / raw)
  To: dev

Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---
 lib/pipeline/rte_swx_ctl.h               |   3 +
 lib/pipeline/rte_swx_pipeline.c          | 164 ++++++++++++++++++++---
 lib/pipeline/rte_swx_pipeline.h          |   7 +-
 lib/pipeline/rte_swx_pipeline_internal.h |  70 +++++++++-
 lib/pipeline/rte_swx_pipeline_spec.c     | 146 ++++++++++++++++----
 5 files changed, 336 insertions(+), 54 deletions(-)

diff --git a/lib/pipeline/rte_swx_ctl.h b/lib/pipeline/rte_swx_ctl.h
index 204026dc0e..e4cdc840fc 100644
--- a/lib/pipeline/rte_swx_ctl.h
+++ b/lib/pipeline/rte_swx_ctl.h
@@ -629,6 +629,9 @@ struct rte_swx_learner_stats {
 	/** Number of packets with learning error. */
 	uint64_t n_pkts_learn_err;
 
+	/** Number of packets with rearm event. */
+	uint64_t n_pkts_rearm;
+
 	/** Number of packets with forget event. */
 	uint64_t n_pkts_forget;
 
diff --git a/lib/pipeline/rte_swx_pipeline.c b/lib/pipeline/rte_swx_pipeline.c
index 17be31d5a4..228f309750 100644
--- a/lib/pipeline/rte_swx_pipeline.c
+++ b/lib/pipeline/rte_swx_pipeline.c
@@ -2583,31 +2583,38 @@ instr_learn_translate(struct rte_swx_pipeline *p,
 		      struct instruction_data *data __rte_unused)
 {
 	struct action *a;
-	const char *mf_name;
-	uint32_t mf_offset = 0;
+	struct field *mf_first_arg = NULL, *mf_timeout_id = NULL;
+	const char *mf_first_arg_name, *mf_timeout_id_name;
 
 	CHECK(action, EINVAL);
-	CHECK((n_tokens == 2) || (n_tokens == 3), EINVAL);
+	CHECK((n_tokens == 3) || (n_tokens == 4), EINVAL);
 
+	/* Action. */
 	a = action_find(p, tokens[1]);
 	CHECK(a, EINVAL);
 	CHECK(!action_has_nbo_args(a), EINVAL);
 
-	mf_name = (n_tokens > 2) ? tokens[2] : NULL;
-	CHECK(!learner_action_args_check(p, a, mf_name), EINVAL);
-
-	if (mf_name) {
-		struct field *mf;
-
-		mf = metadata_field_parse(p, mf_name);
-		CHECK(mf, EINVAL);
+	/* Action first argument. */
+	mf_first_arg_name = (n_tokens == 4) ? tokens[2] : NULL;
+	CHECK(!learner_action_args_check(p, a, mf_first_arg_name), EINVAL);
 
-		mf_offset = mf->offset / 8;
+	if (mf_first_arg_name) {
+		mf_first_arg = metadata_field_parse(p, mf_first_arg_name);
+		CHECK(mf_first_arg, EINVAL);
 	}
 
+	/* Timeout ID. */
+	mf_timeout_id_name = (n_tokens == 4) ? tokens[3] : tokens[2];
+	CHECK_NAME(mf_timeout_id_name, EINVAL);
+	mf_timeout_id = metadata_field_parse(p, mf_timeout_id_name);
+	CHECK(mf_timeout_id, EINVAL);
+
+	/* Instruction. */
 	instr->type = INSTR_LEARNER_LEARN;
 	instr->learn.action_id = a->id;
-	instr->learn.mf_offset = mf_offset;
+	instr->learn.mf_first_arg_offset = mf_first_arg ? (mf_first_arg->offset / 8) : 0;
+	instr->learn.mf_timeout_id_offset = mf_timeout_id->offset / 8;
+	instr->learn.mf_timeout_id_n_bits = mf_timeout_id->n_bits;
 
 	return 0;
 }
@@ -2624,6 +2631,66 @@ instr_learn_exec(struct rte_swx_pipeline *p)
 	thread_ip_inc(p);
 }
 
+/*
+ * rearm.
+ */
+static int
+instr_rearm_translate(struct rte_swx_pipeline *p,
+		      struct action *action,
+		      char **tokens,
+		      int n_tokens,
+		      struct instruction *instr,
+		      struct instruction_data *data __rte_unused)
+{
+	struct field *mf_timeout_id;
+	const char *mf_timeout_id_name;
+
+	CHECK(action, EINVAL);
+	CHECK((n_tokens == 1) || (n_tokens == 2), EINVAL);
+
+	/* INSTR_LEARNER_REARM. */
+	if (n_tokens == 1) {
+		instr->type = INSTR_LEARNER_REARM;
+		return 0;
+	}
+
+	/* INSTR_LEARNER_REARM_NEW. */
+	mf_timeout_id_name = tokens[1];
+	CHECK_NAME(mf_timeout_id_name, EINVAL);
+	mf_timeout_id = metadata_field_parse(p, mf_timeout_id_name);
+	CHECK(mf_timeout_id, EINVAL);
+
+	instr->type = INSTR_LEARNER_REARM_NEW;
+	instr->learn.mf_timeout_id_offset = mf_timeout_id->offset / 8;
+	instr->learn.mf_timeout_id_n_bits = mf_timeout_id->n_bits;
+
+	return 0;
+}
+
+static inline void
+instr_rearm_exec(struct rte_swx_pipeline *p)
+{
+	struct thread *t = &p->threads[p->thread_id];
+	struct instruction *ip = t->ip;
+
+	__instr_rearm_exec(p, t, ip);
+
+	/* Thread. */
+	thread_ip_inc(p);
+}
+
+static inline void
+instr_rearm_new_exec(struct rte_swx_pipeline *p)
+{
+	struct thread *t = &p->threads[p->thread_id];
+	struct instruction *ip = t->ip;
+
+	__instr_rearm_new_exec(p, t, ip);
+
+	/* Thread. */
+	thread_ip_inc(p);
+}
+
 /*
  * forget.
  */
@@ -6051,6 +6118,13 @@ instr_translate(struct rte_swx_pipeline *p,
 					     n_tokens - tpos,
 					     instr,
 					     data);
+	if (!strcmp(tokens[tpos], "rearm"))
+		return instr_rearm_translate(p,
+					     action,
+					     &tokens[tpos],
+					     n_tokens - tpos,
+					     instr,
+					     data);
 
 	if (!strcmp(tokens[tpos], "forget"))
 		return instr_forget_translate(p,
@@ -7040,6 +7114,8 @@ static instr_exec_t instruction_table[] = {
 	[INSTR_LEARNER] = instr_learner_exec,
 	[INSTR_LEARNER_AF] = instr_learner_af_exec,
 	[INSTR_LEARNER_LEARN] = instr_learn_exec,
+	[INSTR_LEARNER_REARM] = instr_rearm_exec,
+	[INSTR_LEARNER_REARM_NEW] = instr_rearm_new_exec,
 	[INSTR_LEARNER_FORGET] = instr_forget_exec,
 	[INSTR_EXTERN_OBJ] = instr_extern_obj_exec,
 	[INSTR_EXTERN_FUNC] = instr_extern_func_exec,
@@ -8546,7 +8622,8 @@ 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)
+			      uint32_t *timeout,
+			      uint32_t n_timeouts)
 {
 	struct learner *l = NULL;
 	struct action *default_action;
@@ -8616,6 +8693,7 @@ rte_swx_pipeline_learner_config(struct rte_swx_pipeline *p,
 	/* Any other checks. */
 	CHECK(size, EINVAL);
 	CHECK(timeout, EINVAL);
+	CHECK(n_timeouts && (n_timeouts < RTE_SWX_TABLE_LEARNER_N_KEY_TIMEOUTS_MAX), EINVAL);
 
 	/* Memory allocation. */
 	l = calloc(1, sizeof(struct learner));
@@ -8702,7 +8780,10 @@ rte_swx_pipeline_learner_config(struct rte_swx_pipeline *p,
 
 	l->size = size;
 
-	l->timeout = timeout;
+	for (i = 0; i < n_timeouts; i++)
+		l->timeout[i] = timeout[i];
+
+	l->n_timeouts = n_timeouts;
 
 	l->id = p->n_learners;
 
@@ -8734,6 +8815,8 @@ learner_params_free(struct rte_swx_table_learner_params *params)
 
 	free(params->key_mask0);
 
+	free(params->key_timeout);
+
 	free(params);
 }
 
@@ -8787,9 +8870,16 @@ learner_params_get(struct learner *l)
 	/* Maximum number of keys. */
 	params->n_keys_max = l->size;
 
+	/* Memory allocation. */
+	params->key_timeout = calloc(l->n_timeouts, sizeof(uint32_t));
+	if (!params->key_timeout)
+		goto error;
+
 	/* Timeout. */
-	params->key_timeout[0] = l->timeout;
-	params->n_key_timeouts = 1;
+	for (i = 0; i < l->n_timeouts; i++)
+		params->key_timeout[i] = l->timeout[i];
+
+	params->n_key_timeouts = l->n_timeouts;
 
 	return params;
 
@@ -10170,6 +10260,7 @@ rte_swx_ctl_pipeline_learner_stats_read(struct rte_swx_pipeline *p,
 	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_rearm = learner_stats->n_pkts_rearm;
 	stats->n_pkts_forget = learner_stats->n_pkts_forget;
 
 	return 0;
@@ -10583,6 +10674,8 @@ instr_type_to_name(struct instruction *instr)
 	case INSTR_LEARNER_AF: return "INSTR_LEARNER_AF";
 
 	case INSTR_LEARNER_LEARN: return "INSTR_LEARNER_LEARN";
+	case INSTR_LEARNER_REARM: return "INSTR_LEARNER_REARM";
+	case INSTR_LEARNER_REARM_NEW: return "INSTR_LEARNER_REARM_NEW";
 	case INSTR_LEARNER_FORGET: return "INSTR_LEARNER_FORGET";
 
 	case INSTR_EXTERN_OBJ: return "INSTR_EXTERN_OBJ";
@@ -11207,11 +11300,40 @@ instr_learn_export(struct instruction *instr, FILE *f)
 		"\t{\n"
 		"\t\t.type = %s,\n"
 		"\t\t.learn = {\n"
-		"\t\t\t\t.action_id = %u,\n"
+		"\t\t\t.action_id = %u,\n"
+		"\t\t\t.mf_first_arg_offset = %u,\n"
+		"\t\t\t.mf_timeout_id_offset = %u,\n"
+		"\t\t\t.mf_timeout_id_n_bits = %u,\n"
 		"\t\t},\n"
 		"\t},\n",
 		instr_type_to_name(instr),
-		instr->learn.action_id);
+		instr->learn.action_id,
+		instr->learn.mf_first_arg_offset,
+		instr->learn.mf_timeout_id_offset,
+		instr->learn.mf_timeout_id_n_bits);
+}
+
+static void
+instr_rearm_export(struct instruction *instr, FILE *f)
+{
+	if (instr->type == INSTR_LEARNER_REARM)
+		fprintf(f,
+			"\t{\n"
+			"\t\t.type = %s,\n"
+			"\t},\n",
+			instr_type_to_name(instr));
+	else
+		fprintf(f,
+			"\t{\n"
+			"\t\t.type = %s,\n"
+			"\t\t.learn = {\n"
+			"\t\t\t.mf_timeout_id_offset = %u,\n"
+			"\t\t\t.mf_timeout_id_n_bits = %u,\n"
+			"\t\t},\n"
+			"\t},\n",
+			instr_type_to_name(instr),
+			instr->learn.mf_timeout_id_offset,
+			instr->learn.mf_timeout_id_n_bits);
 }
 
 static void
@@ -11509,6 +11631,8 @@ static instruction_export_t export_table[] = {
 	[INSTR_LEARNER_AF] = instr_table_export,
 
 	[INSTR_LEARNER_LEARN] = instr_learn_export,
+	[INSTR_LEARNER_REARM] = instr_rearm_export,
+	[INSTR_LEARNER_REARM_NEW] = instr_rearm_export,
 	[INSTR_LEARNER_FORGET] = instr_forget_export,
 
 	[INSTR_EXTERN_OBJ] = instr_extern_export,
@@ -11730,6 +11854,8 @@ instr_type_to_func(struct instruction *instr)
 	case INSTR_LEARNER_AF: return NULL;
 
 	case INSTR_LEARNER_LEARN: return "__instr_learn_exec";
+	case INSTR_LEARNER_REARM: return "__instr_rearm_exec";
+	case INSTR_LEARNER_REARM_NEW: return "__instr_rearm_new_exec";
 	case INSTR_LEARNER_FORGET: return "__instr_forget_exec";
 
 	case INSTR_EXTERN_OBJ: return NULL;
diff --git a/lib/pipeline/rte_swx_pipeline.h b/lib/pipeline/rte_swx_pipeline.h
index c95d0c7682..a5a0954915 100644
--- a/lib/pipeline/rte_swx_pipeline.h
+++ b/lib/pipeline/rte_swx_pipeline.h
@@ -786,7 +786,9 @@ struct rte_swx_pipeline_learner_params {
  * @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.
+ *   Array of possible table entry timeouts in seconds. Must be non-NULL.
+ * @param[in] n_timeouts
+ *   Number of elements in the *timeout* array.
  * @return
  *   0 on success or the following error codes otherwise:
  *   -EINVAL: Invalid argument;
@@ -800,7 +802,8 @@ 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);
+				uint32_t *timeout,
+				uint32_t n_timeouts);
 
 /**
  * Pipeline register array configure
diff --git a/lib/pipeline/rte_swx_pipeline_internal.h b/lib/pipeline/rte_swx_pipeline_internal.h
index 51bb464f5f..f8a6661f75 100644
--- a/lib/pipeline/rte_swx_pipeline_internal.h
+++ b/lib/pipeline/rte_swx_pipeline_internal.h
@@ -476,9 +476,13 @@ enum instruction_type {
 	INSTR_LEARNER,
 	INSTR_LEARNER_AF,
 
-	/* learn LEARNER ACTION_NAME [ m.action_first_arg ] */
+	/* learn ACTION_NAME [ m.action_first_arg ] m.timeout_id */
 	INSTR_LEARNER_LEARN,
 
+	/* rearm [ m.timeout_id ] */
+	INSTR_LEARNER_REARM,
+	INSTR_LEARNER_REARM_NEW,
+
 	/* forget */
 	INSTR_LEARNER_FORGET,
 
@@ -611,7 +615,9 @@ struct instr_table {
 
 struct instr_learn {
 	uint8_t action_id;
-	uint8_t mf_offset;
+	uint8_t mf_first_arg_offset;
+	uint8_t mf_timeout_id_offset;
+	uint8_t mf_timeout_id_n_bits;
 };
 
 struct instr_extern_obj {
@@ -850,7 +856,8 @@ struct learner {
 	int *action_is_for_default_entry;
 
 	uint32_t size;
-	uint32_t timeout;
+	uint32_t timeout[RTE_SWX_TABLE_LEARNER_N_KEY_TIMEOUTS_MAX];
+	uint32_t n_timeouts;
 	uint32_t id;
 };
 
@@ -864,6 +871,7 @@ struct learner_runtime {
 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_rearm;
 	uint64_t n_pkts_forget;
 	uint64_t *n_pkts_action;
 };
@@ -2202,7 +2210,9 @@ __instr_learn_exec(struct rte_swx_pipeline *p,
 		   const struct instruction *ip)
 {
 	uint64_t action_id = ip->learn.action_id;
-	uint32_t mf_offset = ip->learn.mf_offset;
+	uint32_t mf_first_arg_offset = ip->learn.mf_first_arg_offset;
+	uint32_t timeout_id = METADATA_READ(t, ip->learn.mf_timeout_id_offset,
+		ip->learn.mf_timeout_id_n_bits);
 	uint32_t learner_id = t->learner_id;
 	struct rte_swx_table_state *ts = &t->table_state[p->n_tables +
 		p->n_selectors + learner_id];
@@ -2215,8 +2225,8 @@ __instr_learn_exec(struct rte_swx_pipeline *p,
 					   l->mailbox,
 					   t->time,
 					   action_id,
-					   &t->metadata[mf_offset],
-					   0);
+					   &t->metadata[mf_first_arg_offset],
+					   timeout_id);
 
 	TRACE("[Thread %2u] learner %u learn %s\n",
 	      p->thread_id,
@@ -2226,6 +2236,54 @@ __instr_learn_exec(struct rte_swx_pipeline *p,
 	stats->n_pkts_learn[status] += 1;
 }
 
+/*
+ * rearm.
+ */
+static inline void
+__instr_rearm_exec(struct rte_swx_pipeline *p,
+		   struct thread *t,
+		   const struct instruction *ip __rte_unused)
+{
+	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_rearm(ts->obj, l->mailbox, t->time);
+
+	TRACE("[Thread %2u] learner %u rearm\n",
+	      p->thread_id,
+	      learner_id);
+
+	stats->n_pkts_rearm += 1;
+}
+
+static inline void
+__instr_rearm_new_exec(struct rte_swx_pipeline *p,
+		       struct thread *t,
+		       const struct instruction *ip)
+{
+	uint32_t timeout_id = METADATA_READ(t, ip->learn.mf_timeout_id_offset,
+		ip->learn.mf_timeout_id_n_bits);
+	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_rearm_new(ts->obj, l->mailbox, t->time, timeout_id);
+
+	TRACE("[Thread %2u] learner %u rearm with timeout ID %u\n",
+	      p->thread_id,
+	      learner_id,
+	      timeout_id);
+
+	stats->n_pkts_rearm += 1;
+}
+
 /*
  * forget.
  */
diff --git a/lib/pipeline/rte_swx_pipeline_spec.c b/lib/pipeline/rte_swx_pipeline_spec.c
index b2f2469b18..904b9eb471 100644
--- a/lib/pipeline/rte_swx_pipeline_spec.c
+++ b/lib/pipeline/rte_swx_pipeline_spec.c
@@ -29,7 +29,8 @@
 #define LEARNER_BLOCK 7
 #define LEARNER_KEY_BLOCK 8
 #define LEARNER_ACTIONS_BLOCK 9
-#define APPLY_BLOCK 10
+#define LEARNER_TIMEOUT_BLOCK 10
+#define APPLY_BLOCK 11
 
 /*
  * extobj.
@@ -1395,14 +1396,18 @@ selector_block_parse(struct selector_spec *s,
  *	}
  *	default_action ACTION_NAME args none | ARG0_NAME ARG0_VALUE ... [ const ]
  *	size SIZE
- *	timeout TIMEOUT_IN_SECONDS
+ *	timeout {
+ *		TIMEOUT_IN_SECONDS
+ *		...
+ *	}
  * }
  */
 struct learner_spec {
 	char *name;
 	struct rte_swx_pipeline_learner_params params;
 	uint32_t size;
-	uint32_t timeout;
+	uint32_t *timeout;
+	uint32_t n_timeouts;
 };
 
 static void
@@ -1457,7 +1462,10 @@ learner_spec_free(struct learner_spec *s)
 
 	s->size = 0;
 
-	s->timeout = 0;
+	free(s->timeout);
+	s->timeout = NULL;
+
+	s->n_timeouts = 0;
 }
 
 static int
@@ -1719,6 +1727,95 @@ learner_default_action_statement_parse(struct learner_spec *s,
 	return status;
 }
 
+static int
+learner_timeout_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 timeout statement.";
+		return -EINVAL;
+	}
+
+	/* block_mask. */
+	*block_mask |= 1 << LEARNER_TIMEOUT_BLOCK;
+
+	return 0;
+}
+
+static int
+learner_timeout_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)
+{
+	uint32_t *new_timeout = NULL;
+	char *str;
+	uint32_t val;
+	int status = 0;
+
+	/* Handle end of block. */
+	if ((n_tokens == 1) && !strcmp(tokens[0], "}")) {
+		*block_mask &= ~(1 << LEARNER_TIMEOUT_BLOCK);
+		return 0;
+	}
+
+	/* Check input arguments. */
+	if (n_tokens != 1) {
+		status = -EINVAL;
+		goto error;
+	}
+
+	str = tokens[0];
+	val = strtoul(str, &str, 0);
+	if (str[0]) {
+		status = -EINVAL;
+		goto error;
+	}
+
+	new_timeout = realloc(s->timeout, (s->n_timeouts + 1) * sizeof(uint32_t));
+	if (!new_timeout) {
+		status = -ENOMEM;
+		goto error;
+	}
+
+	s->timeout = new_timeout;
+	s->timeout[s->n_timeouts] = val;
+	s->n_timeouts++;
+
+	return 0;
+
+error:
+	free(new_timeout);
+
+	if (err_line)
+		*err_line = n_lines;
+
+	if (err_msg)
+		switch (status) {
+		case -ENOMEM:
+			*err_msg = "Memory allocation failed.";
+			break;
+
+		default:
+			*err_msg = "Invalid timeout value statement.";
+			break;
+		}
+
+	return status;
+}
+
+
 static int
 learner_statement_parse(struct learner_spec *s,
 		      uint32_t *block_mask,
@@ -1780,6 +1877,15 @@ learner_block_parse(struct learner_spec *s,
 						   err_line,
 						   err_msg);
 
+	if (*block_mask & (1 << LEARNER_TIMEOUT_BLOCK))
+		return learner_timeout_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);
@@ -1833,28 +1939,13 @@ learner_block_parse(struct learner_spec *s,
 		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;
-	}
+	if (!strcmp(tokens[0], "timeout"))
+		return learner_timeout_statement_parse(block_mask,
+						       tokens,
+						       n_tokens,
+						       n_lines,
+						       err_line,
+						       err_msg);
 
 	/* Anything else. */
 	if (err_line)
@@ -2365,7 +2456,8 @@ rte_swx_pipeline_build_from_spec(struct rte_swx_pipeline *p,
 				learner_spec.name,
 				&learner_spec.params,
 				learner_spec.size,
-				learner_spec.timeout);
+				learner_spec.timeout,
+				learner_spec.n_timeouts);
 			if (status) {
 				if (err_line)
 					*err_line = n_lines;
-- 
2.17.1


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH 3/3] examples/pipeline: improve learner table timers
  2022-04-21 15:59 [PATCH 1/3] table: improve learner table timers Cristian Dumitrescu
  2022-04-21 15:59 ` [PATCH 2/3] pipeline: " Cristian Dumitrescu
@ 2022-04-21 15:59 ` Cristian Dumitrescu
  2022-04-22 13:03 ` [PATCH V2 1/3] table: " Cristian Dumitrescu
  2 siblings, 0 replies; 10+ messages in thread
From: Cristian Dumitrescu @ 2022-04-21 15:59 UTC (permalink / raw)
  To: dev

Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---
 examples/pipeline/cli.c                 |  2 ++
 examples/pipeline/examples/learner.spec | 15 +++++++++++++--
 2 files changed, 15 insertions(+), 2 deletions(-)

diff --git a/examples/pipeline/cli.c b/examples/pipeline/cli.c
index d52ad6b61e..0334616bd9 100644
--- a/examples/pipeline/cli.c
+++ b/examples/pipeline/cli.c
@@ -2677,12 +2677,14 @@ cmd_pipeline_stats(char **tokens,
 			"\t\tMiss (packets): %" PRIu64 "\n"
 			"\t\tLearn OK (packets): %" PRIu64 "\n"
 			"\t\tLearn error (packets): %" PRIu64 "\n"
+			"\t\tRearm (packets): %" PRIu64 "\n"
 			"\t\tForget (packets): %" PRIu64 "\n",
 			learner_info.name,
 			stats.n_pkts_hit,
 			stats.n_pkts_miss,
 			stats.n_pkts_learn_ok,
 			stats.n_pkts_learn_err,
+			stats.n_pkts_rearm,
 			stats.n_pkts_forget);
 		out_size -= strlen(out);
 		out += strlen(out);
diff --git a/examples/pipeline/examples/learner.spec b/examples/pipeline/examples/learner.spec
index 4ee52da7ac..095325c293 100644
--- a/examples/pipeline/examples/learner.spec
+++ b/examples/pipeline/examples/learner.spec
@@ -48,6 +48,9 @@ struct metadata_t {
 	bit<32> port_in
 	bit<32> port_out
 
+	// Key timeout.
+	bit<32> timeout_id
+
 	// Arguments for the "fwd_action" action.
 	bit<32> fwd_action_arg_port_out
 }
@@ -68,10 +71,14 @@ struct fwd_action_args_t {
 
 action fwd_action args instanceof fwd_action_args_t {
 	mov m.port_out t.port_out
+	rearm
 	return
 }
 
 action learn_action args none {
+	// Pick the key timeout. Timeout ID #1 (i.e. 120 seconds) is selected.
+	mov m.timeout_id 1
+
 	// Read current counter value into m.fwd_action_arg_port_out.
 	regrd m.fwd_action_arg_port_out counter 0
 
@@ -84,7 +91,7 @@ action learn_action args none {
 	// Add the current lookup key to the table with fwd_action as the key action. The action
 	// arguments are read from the packet meta-data (the m.fwd_action_arg_port_out field). These
 	// packet meta-data fields have to be written before the "learn" instruction is invoked.
-	learn fwd_action m.fwd_action_arg_port_out
+	learn fwd_action m.fwd_action_arg_port_out m.timeout_id
 
 	// Send the current packet to the same output port.
 	mov m.port_out m.fwd_action_arg_port_out
@@ -110,7 +117,11 @@ learner fwd_table {
 
 	size 1048576
 
-	timeout 120
+	timeout {
+		60
+		120
+		180
+	}
 }
 
 //
-- 
2.17.1


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH V2 1/3] table: improve learner table timers
  2022-04-21 15:59 [PATCH 1/3] table: improve learner table timers Cristian Dumitrescu
  2022-04-21 15:59 ` [PATCH 2/3] pipeline: " Cristian Dumitrescu
  2022-04-21 15:59 ` [PATCH 3/3] examples/pipeline: " Cristian Dumitrescu
@ 2022-04-22 13:03 ` Cristian Dumitrescu
  2022-04-22 13:03   ` [PATCH V2 2/3] pipeline: " Cristian Dumitrescu
                     ` (2 more replies)
  2 siblings, 3 replies; 10+ messages in thread
From: Cristian Dumitrescu @ 2022-04-22 13:03 UTC (permalink / raw)
  To: dev

Previously, on lookup hit, the hit key had its timer automatically
rearmed with the same timeout in order to prevent its expiration. Now,
a broader set of actions is available on lookup hit, which has to be
managed explicitly: the key can have its timer rearmed with the same
or with a different timeout, or the key timer can be left unmodified.
The latter option allows the key to expire naturally when the timer
eventually runs out, unless the key is hit again and its timer rearmed
at that point. Needed by the TCP connection tracking state machine.

Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---
Depends-on: series-22386 ("[V5,1/6] port: support packet mirroring")
Depends-on: patch-22480 ("[V4] pipeline: support default action arguments")

 lib/pipeline/rte_swx_pipeline.c          |   3 +-
 lib/pipeline/rte_swx_pipeline_internal.h |   3 +-
 lib/table/rte_swx_table_learner.c        | 110 ++++++++++++++++++++---
 lib/table/rte_swx_table_learner.h        |  90 +++++++++++++++++--
 lib/table/version.map                    |   4 +
 5 files changed, 190 insertions(+), 20 deletions(-)

diff --git a/lib/pipeline/rte_swx_pipeline.c b/lib/pipeline/rte_swx_pipeline.c
index dfbac929c7..17be31d5a4 100644
--- a/lib/pipeline/rte_swx_pipeline.c
+++ b/lib/pipeline/rte_swx_pipeline.c
@@ -8788,7 +8788,8 @@ learner_params_get(struct learner *l)
 	params->n_keys_max = l->size;
 
 	/* Timeout. */
-	params->key_timeout = l->timeout;
+	params->key_timeout[0] = l->timeout;
+	params->n_key_timeouts = 1;
 
 	return params;
 
diff --git a/lib/pipeline/rte_swx_pipeline_internal.h b/lib/pipeline/rte_swx_pipeline_internal.h
index 381a35c6e0..51bb464f5f 100644
--- a/lib/pipeline/rte_swx_pipeline_internal.h
+++ b/lib/pipeline/rte_swx_pipeline_internal.h
@@ -2215,7 +2215,8 @@ __instr_learn_exec(struct rte_swx_pipeline *p,
 					   l->mailbox,
 					   t->time,
 					   action_id,
-					   &t->metadata[mf_offset]);
+					   &t->metadata[mf_offset],
+					   0);
 
 	TRACE("[Thread %2u] learner %u learn %s\n",
 	      p->thread_id,
diff --git a/lib/table/rte_swx_table_learner.c b/lib/table/rte_swx_table_learner.c
index 15576c2aa3..3c98b8ce81 100644
--- a/lib/table/rte_swx_table_learner.c
+++ b/lib/table/rte_swx_table_learner.c
@@ -230,12 +230,16 @@ table_keycmp(void *a, void *b, void *b_mask, uint32_t n_bytes)
 
 #define TABLE_KEYS_PER_BUCKET 4
 
+#define TABLE_BUCKET_USEFUL_SIZE \
+	(TABLE_KEYS_PER_BUCKET * (sizeof(uint32_t) + sizeof(uint32_t) + sizeof(uint8_t)))
+
 #define TABLE_BUCKET_PAD_SIZE \
-	(RTE_CACHE_LINE_SIZE - TABLE_KEYS_PER_BUCKET * (sizeof(uint32_t) + sizeof(uint32_t)))
+	(RTE_CACHE_LINE_SIZE - TABLE_BUCKET_USEFUL_SIZE)
 
 struct table_bucket {
 	uint32_t time[TABLE_KEYS_PER_BUCKET];
 	uint32_t sig[TABLE_KEYS_PER_BUCKET];
+	uint8_t key_timeout_id[TABLE_KEYS_PER_BUCKET];
 	uint8_t pad[TABLE_BUCKET_PAD_SIZE];
 	uint8_t key[0];
 };
@@ -284,8 +288,11 @@ struct table_params {
 	/* log2(bucket_size). Purpose: avoid multiplication with non-power of 2 numbers. */
 	size_t bucket_size_log2;
 
-	/* Timeout in CPU clock cycles. */
-	uint64_t key_timeout;
+	/* Set of all possible key timeout values measured in CPU clock cycles. */
+	uint64_t key_timeout[RTE_SWX_TABLE_LEARNER_N_KEY_TIMEOUTS_MAX];
+
+	/* Number of key timeout values. */
+	uint32_t n_key_timeouts;
 
 	/* Total memory size. */
 	size_t total_size;
@@ -305,15 +312,23 @@ struct table {
 static int
 table_params_get(struct table_params *p, struct rte_swx_table_learner_params *params)
 {
+	uint32_t i;
+
 	/* Check input parameters. */
 	if (!params ||
 	    !params->key_size ||
 	    (params->key_size > 64) ||
 	    !params->n_keys_max ||
 	    (params->n_keys_max > 1U << 31) ||
-	    !params->key_timeout)
+	    !params->key_timeout ||
+	    !params->n_key_timeouts ||
+	    (params->n_key_timeouts > RTE_SWX_TABLE_LEARNER_N_KEY_TIMEOUTS_MAX))
 		return -EINVAL;
 
+	for (i = 0; i < params->n_key_timeouts; i++)
+		if (!params->key_timeout[i])
+			return -EINVAL;
+
 	/* Key. */
 	p->key_size = params->key_size;
 
@@ -346,7 +361,17 @@ table_params_get(struct table_params *p, struct rte_swx_table_learner_params *pa
 	p->bucket_size_log2 = __builtin_ctzll(p->bucket_size);
 
 	/* Timeout. */
-	p->key_timeout = params->key_timeout * rte_get_tsc_hz();
+	for (i = 0; i < params->n_key_timeouts; i++) {
+		p->key_timeout[i] = params->key_timeout[i] * rte_get_tsc_hz();
+
+		if (!(p->key_timeout[i] >> 32))
+			p->key_timeout[i] = 1LLU << 32;
+	}
+
+	p->n_key_timeouts = rte_align32pow2(params->n_key_timeouts);
+
+	for ( ; i < p->n_key_timeouts; i++)
+		p->key_timeout[i] = p->key_timeout[0];
 
 	/* Total size. */
 	p->total_size = sizeof(struct table) + p->n_buckets * p->bucket_size;
@@ -505,8 +530,6 @@ rte_swx_table_learner_lookup(void *table,
 				/* Hit. */
 				rte_prefetch0(data);
 
-				b->time[i] = (input_time + t->params.key_timeout) >> 32;
-
 				m->hit = 1;
 				m->bucket_key_pos = i;
 				m->state = 0;
@@ -536,23 +559,83 @@ rte_swx_table_learner_lookup(void *table,
 	}
 }
 
+void
+rte_swx_table_learner_rearm(void *table,
+			    void *mailbox,
+			    uint64_t input_time)
+{
+	struct table *t = table;
+	struct mailbox *m = mailbox;
+	struct table_bucket *b;
+	size_t bucket_key_pos;
+	uint64_t key_timeout;
+	uint32_t key_timeout_id;
+
+	if (!m->hit)
+		return;
+
+	b = m->bucket;
+	bucket_key_pos = m->bucket_key_pos;
+
+	key_timeout_id = b->key_timeout_id[bucket_key_pos];
+	key_timeout = t->params.key_timeout[key_timeout_id];
+	b->time[bucket_key_pos] = (input_time + key_timeout) >> 32;
+}
+
+void
+rte_swx_table_learner_rearm_new(void *table,
+				void *mailbox,
+				uint64_t input_time,
+				uint32_t key_timeout_id)
+{
+	struct table *t = table;
+	struct mailbox *m = mailbox;
+	struct table_bucket *b;
+	size_t bucket_key_pos;
+	uint64_t key_timeout;
+
+	if (!m->hit)
+		return;
+
+	b = m->bucket;
+	bucket_key_pos = m->bucket_key_pos;
+
+	key_timeout_id &= t->params.n_key_timeouts - 1;
+	key_timeout = t->params.key_timeout[key_timeout_id];
+	b->time[bucket_key_pos] = (input_time + key_timeout) >> 32;
+	b->key_timeout_id[bucket_key_pos] = (uint8_t)key_timeout_id;
+}
+
 uint32_t
 rte_swx_table_learner_add(void *table,
 			  void *mailbox,
 			  uint64_t input_time,
 			  uint64_t action_id,
-			  uint8_t *action_data)
+			  uint8_t *action_data,
+			  uint32_t key_timeout_id)
 {
 	struct table *t = table;
 	struct mailbox *m = mailbox;
 	struct table_bucket *b = m->bucket;
+	uint64_t key_timeout;
 	uint32_t i;
 
-	/* Lookup hit: The key, key signature and key time are already properly configured (the key
-	 * time was bumped by lookup), only the key data need to be updated.
+	/* Adjust the key timeout ID to fit the valid range. */
+	key_timeout_id &= t->params.n_key_timeouts - 1;
+	key_timeout = t->params.key_timeout[key_timeout_id];
+
+	/* Lookup hit: The following bucket fields need to be updated:
+	 * - key (key, sig): NO (already correctly set).
+	 * - key timeout (key_timeout_id, time): YES.
+	 * - key data (data): YES.
 	 */
 	if (m->hit) {
-		uint64_t *data = table_bucket_data_get(t, b, m->bucket_key_pos);
+		size_t bucket_key_pos = m->bucket_key_pos;
+		uint64_t *data = table_bucket_data_get(t, b, bucket_key_pos);
+
+		/* Install the key timeout. */
+		b->time[bucket_key_pos] = (input_time + key_timeout) >> 32;
+		b->key_timeout_id[bucket_key_pos] = (uint8_t)key_timeout_id;
 
 		/* Install the key data. */
 		data[0] = action_id;
@@ -576,9 +659,10 @@ rte_swx_table_learner_add(void *table,
 			uint8_t *key = table_bucket_key_get(t, b, i);
 			uint64_t *data = table_bucket_data_get(t, b, i);
 
-			/* Install the key. */
-			b->time[i] = (input_time + t->params.key_timeout) >> 32;
+			/* Install the key and the key timeout. */
+			b->time[i] = (input_time + key_timeout) >> 32;
 			b->sig[i] = m->input_sig;
+			b->key_timeout_id[i] = (uint8_t)key_timeout_id;
 			memcpy(key, m->input_key, t->params.key_size);
 
 			/* Install the key data. */
diff --git a/lib/table/rte_swx_table_learner.h b/lib/table/rte_swx_table_learner.h
index eb9d7689fd..8b3128ec5d 100644
--- a/lib/table/rte_swx_table_learner.h
+++ b/lib/table/rte_swx_table_learner.h
@@ -18,13 +18,43 @@ extern "C" {
  * implementation of the "add on miss" scenario: whenever the lookup key is not found in the table
  * (lookup miss), the data plane can decide to add this key to the table with a given action with no
  * control plane intervention. Likewise, the table keys expire based on a configurable timeout and
- * are automatically deleted from the table with no control plane intervention.
+ * are thus automatically removed from the table with no control plane intervention.
+ *
+ * The keys are not automatically rearmed on lookup hit. To delay the key expiration, the key timer
+ * has to be explicitly reinitialized on lookup hit. The key will be kept in the table as long as it
+ * is frequently hit and explicitly rearmed on every hit.
+ *
+ * Operation overview:
+ * 1) Lookup miss:
+ *      a) add: Add the current input key (the key that missed the lookup) to the table with given
+ *              action, action parameters and expiration timeout. This is the way to populate the
+ *              table (which is empty initially). Data plane operation.
+ *      b) Do nothing: Keep the current input key out of the table.
+ * 2) Lookup hit:
+ *      a) add: Update the action, action parameters and/or the expiration timeout for the current
+ *              input key, which is already in the table. The expiration timer of the key is
+ *              automatically rearmed. Data plane operation.
+ *      b) rearm: Rearm the expiration timer for the current input key, which is already in the
+ *              table. The timeout value used for the expiration timer is either the same as the one
+ *              currently associated with the key or a new one can be provided as input. Data plane
+ *              operation.
+ *      c) delete: Delete the current input key from the table. The purpose of this operation is to
+ *              force the deletion of the key from the table before the key expires on timeout due
+ *              to inactivity. Data plane operation.
+ *      d) Do nothing: Keep the expiration timer of the current input key running down. This key
+ *              will thus expire naturally, unless it is hit again as part of a subsequent lookup
+ *              operation, when the key timer can be rearmed or re-added to prolong its life.
  */
 
 #include <stdint.h>
 
 #include <rte_compat.h>
 
+/** Maximum number of key timeout values per learner table. */
+#ifndef RTE_SWX_TABLE_LEARNER_N_KEY_TIMEOUTS_MAX
+#define RTE_SWX_TABLE_LEARNER_N_KEY_TIMEOUTS_MAX 16
+#endif
+
 /** Learner table creation parameters. */
 struct rte_swx_table_learner_params {
 	/** Key size in bytes. Must be non-zero. */
@@ -50,10 +80,16 @@ struct rte_swx_table_learner_params {
 	/** Maximum number of keys to be stored in the table together with their associated data. */
 	uint32_t n_keys_max;
 
-	/** Key timeout in seconds. Must be non-zero. Each table key expires and is automatically
-	 * deleted from the table after this many seconds.
+	/** The set of all possible key timeout values measured in seconds. Each value must be
+	 * non-zero. Each table key expires and is automatically deleted from the table after
+	 * this many seconds.
 	 */
-	uint32_t key_timeout;
+	uint32_t *key_timeout;
+
+	/** Number of possible key timeout values present in the *key_timeout* set. It must be less
+	 * than or equal to *RTE_SWX_TABLE_LEARNER_N_KEY_TIMEOUTS_MAX*.
+	 */
+	uint32_t n_key_timeouts;
 };
 
 /**
@@ -160,6 +196,8 @@ rte_swx_table_learner_lookup(void *table,
  *   ID of the action associated with the key.
  * @param[out] action_data
  *   Action data for the *action_id* action.
+ * @param[in] key_timeout_id
+ *   Key timeout ID.
  * @return
  *   0 on success, 1 or error (table full).
  */
@@ -169,7 +207,49 @@ rte_swx_table_learner_add(void *table,
 			  void *mailbox,
 			  uint64_t time,
 			  uint64_t action_id,
-			  uint8_t *action_data);
+			  uint8_t *action_data,
+			  uint32_t key_timeout_id);
+
+/**
+ * Learner table key rearm with same timeout value
+ *
+ * This operation takes the latest key that was looked up in the table and, in case of lookup hit,
+ * it rearms its expiration timer using the same timeout value currently associated with the key.
+ *
+ * @param[in] table
+ *   Table handle.
+ * @param[in] mailbox
+ *   Mailbox for the current operation.
+ * @param[in] time
+ *   Current time measured in CPU clock cycles.
+ */
+__rte_experimental
+void
+rte_swx_table_learner_rearm(void *table,
+			    void *mailbox,
+			    uint64_t time);
+
+/**
+ * Learner table key rearm with given timeout value
+ *
+ * This operation takes the latest key that was looked up in the table and, in case of lookup hit,
+ * it rearms its expiration timer using the given timeout value.
+ *
+ * @param[in] table
+ *   Table handle.
+ * @param[in] mailbox
+ *   Mailbox for the current operation.
+ * @param[in] time
+ *   Current time measured in CPU clock cycles.
+ * @param[in] key_timeout_id
+ *   Key timeout ID.
+ */
+__rte_experimental
+void
+rte_swx_table_learner_rearm_new(void *table,
+				void *mailbox,
+				uint64_t time,
+				uint32_t key_timeout_id);
 
 /**
  * Learner table key delete
diff --git a/lib/table/version.map b/lib/table/version.map
index efe5f6e52c..d27d649332 100644
--- a/lib/table/version.map
+++ b/lib/table/version.map
@@ -45,4 +45,8 @@ EXPERIMENTAL {
 	rte_swx_table_learner_free;
 	rte_swx_table_learner_lookup;
 	rte_swx_table_learner_mailbox_size_get;
+
+	#added in 22.07
+	rte_swx_table_learner_rearm;
+	rte_swx_table_learner_rearm_new;
 };
-- 
2.17.1


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH V2 2/3] pipeline: improve learner table timers
  2022-04-22 13:03 ` [PATCH V2 1/3] table: " Cristian Dumitrescu
@ 2022-04-22 13:03   ` Cristian Dumitrescu
  2022-04-22 13:03   ` [PATCH V2 3/3] examples/pipeline: " Cristian Dumitrescu
  2022-05-20 22:12   ` [PATCH V3 1/3] table: " Cristian Dumitrescu
  2 siblings, 0 replies; 10+ messages in thread
From: Cristian Dumitrescu @ 2022-04-22 13:03 UTC (permalink / raw)
  To: dev

Enable the pipeline to use the improved learner table timer operation
through the new "rearm" instruction.

Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---
 lib/pipeline/rte_swx_ctl.h               |   3 +
 lib/pipeline/rte_swx_pipeline.c          | 166 ++++++++++++++++++++---
 lib/pipeline/rte_swx_pipeline.h          |   7 +-
 lib/pipeline/rte_swx_pipeline_internal.h |  70 +++++++++-
 lib/pipeline/rte_swx_pipeline_spec.c     | 146 ++++++++++++++++----
 5 files changed, 337 insertions(+), 55 deletions(-)

diff --git a/lib/pipeline/rte_swx_ctl.h b/lib/pipeline/rte_swx_ctl.h
index 204026dc0e..e4cdc840fc 100644
--- a/lib/pipeline/rte_swx_ctl.h
+++ b/lib/pipeline/rte_swx_ctl.h
@@ -629,6 +629,9 @@ struct rte_swx_learner_stats {
 	/** Number of packets with learning error. */
 	uint64_t n_pkts_learn_err;
 
+	/** Number of packets with rearm event. */
+	uint64_t n_pkts_rearm;
+
 	/** Number of packets with forget event. */
 	uint64_t n_pkts_forget;
 
diff --git a/lib/pipeline/rte_swx_pipeline.c b/lib/pipeline/rte_swx_pipeline.c
index 17be31d5a4..84d2c24311 100644
--- a/lib/pipeline/rte_swx_pipeline.c
+++ b/lib/pipeline/rte_swx_pipeline.c
@@ -2556,7 +2556,7 @@ instr_learner_af_exec(struct rte_swx_pipeline *p)
 	stats->n_pkts_action[action_id] = n_pkts_action + 1;
 
 	/* Thread. */
-	thread_ip_action_call(p, t, action_id);
+	thread_ip_inc(p);
 
 	/* Action */
 	action_func(p);
@@ -2583,31 +2583,38 @@ instr_learn_translate(struct rte_swx_pipeline *p,
 		      struct instruction_data *data __rte_unused)
 {
 	struct action *a;
-	const char *mf_name;
-	uint32_t mf_offset = 0;
+	struct field *mf_first_arg = NULL, *mf_timeout_id = NULL;
+	const char *mf_first_arg_name, *mf_timeout_id_name;
 
 	CHECK(action, EINVAL);
-	CHECK((n_tokens == 2) || (n_tokens == 3), EINVAL);
+	CHECK((n_tokens == 3) || (n_tokens == 4), EINVAL);
 
+	/* Action. */
 	a = action_find(p, tokens[1]);
 	CHECK(a, EINVAL);
 	CHECK(!action_has_nbo_args(a), EINVAL);
 
-	mf_name = (n_tokens > 2) ? tokens[2] : NULL;
-	CHECK(!learner_action_args_check(p, a, mf_name), EINVAL);
-
-	if (mf_name) {
-		struct field *mf;
-
-		mf = metadata_field_parse(p, mf_name);
-		CHECK(mf, EINVAL);
+	/* Action first argument. */
+	mf_first_arg_name = (n_tokens == 4) ? tokens[2] : NULL;
+	CHECK(!learner_action_args_check(p, a, mf_first_arg_name), EINVAL);
 
-		mf_offset = mf->offset / 8;
+	if (mf_first_arg_name) {
+		mf_first_arg = metadata_field_parse(p, mf_first_arg_name);
+		CHECK(mf_first_arg, EINVAL);
 	}
 
+	/* Timeout ID. */
+	mf_timeout_id_name = (n_tokens == 4) ? tokens[3] : tokens[2];
+	CHECK_NAME(mf_timeout_id_name, EINVAL);
+	mf_timeout_id = metadata_field_parse(p, mf_timeout_id_name);
+	CHECK(mf_timeout_id, EINVAL);
+
+	/* Instruction. */
 	instr->type = INSTR_LEARNER_LEARN;
 	instr->learn.action_id = a->id;
-	instr->learn.mf_offset = mf_offset;
+	instr->learn.mf_first_arg_offset = mf_first_arg ? (mf_first_arg->offset / 8) : 0;
+	instr->learn.mf_timeout_id_offset = mf_timeout_id->offset / 8;
+	instr->learn.mf_timeout_id_n_bits = mf_timeout_id->n_bits;
 
 	return 0;
 }
@@ -2624,6 +2631,66 @@ instr_learn_exec(struct rte_swx_pipeline *p)
 	thread_ip_inc(p);
 }
 
+/*
+ * rearm.
+ */
+static int
+instr_rearm_translate(struct rte_swx_pipeline *p,
+		      struct action *action,
+		      char **tokens,
+		      int n_tokens,
+		      struct instruction *instr,
+		      struct instruction_data *data __rte_unused)
+{
+	struct field *mf_timeout_id;
+	const char *mf_timeout_id_name;
+
+	CHECK(action, EINVAL);
+	CHECK((n_tokens == 1) || (n_tokens == 2), EINVAL);
+
+	/* INSTR_LEARNER_REARM. */
+	if (n_tokens == 1) {
+		instr->type = INSTR_LEARNER_REARM;
+		return 0;
+	}
+
+	/* INSTR_LEARNER_REARM_NEW. */
+	mf_timeout_id_name = tokens[1];
+	CHECK_NAME(mf_timeout_id_name, EINVAL);
+	mf_timeout_id = metadata_field_parse(p, mf_timeout_id_name);
+	CHECK(mf_timeout_id, EINVAL);
+
+	instr->type = INSTR_LEARNER_REARM_NEW;
+	instr->learn.mf_timeout_id_offset = mf_timeout_id->offset / 8;
+	instr->learn.mf_timeout_id_n_bits = mf_timeout_id->n_bits;
+
+	return 0;
+}
+
+static inline void
+instr_rearm_exec(struct rte_swx_pipeline *p)
+{
+	struct thread *t = &p->threads[p->thread_id];
+	struct instruction *ip = t->ip;
+
+	__instr_rearm_exec(p, t, ip);
+
+	/* Thread. */
+	thread_ip_inc(p);
+}
+
+static inline void
+instr_rearm_new_exec(struct rte_swx_pipeline *p)
+{
+	struct thread *t = &p->threads[p->thread_id];
+	struct instruction *ip = t->ip;
+
+	__instr_rearm_new_exec(p, t, ip);
+
+	/* Thread. */
+	thread_ip_inc(p);
+}
+
 /*
  * forget.
  */
@@ -6051,6 +6118,13 @@ instr_translate(struct rte_swx_pipeline *p,
 					     n_tokens - tpos,
 					     instr,
 					     data);
+	if (!strcmp(tokens[tpos], "rearm"))
+		return instr_rearm_translate(p,
+					     action,
+					     &tokens[tpos],
+					     n_tokens - tpos,
+					     instr,
+					     data);
 
 	if (!strcmp(tokens[tpos], "forget"))
 		return instr_forget_translate(p,
@@ -7040,6 +7114,8 @@ static instr_exec_t instruction_table[] = {
 	[INSTR_LEARNER] = instr_learner_exec,
 	[INSTR_LEARNER_AF] = instr_learner_af_exec,
 	[INSTR_LEARNER_LEARN] = instr_learn_exec,
+	[INSTR_LEARNER_REARM] = instr_rearm_exec,
+	[INSTR_LEARNER_REARM_NEW] = instr_rearm_new_exec,
 	[INSTR_LEARNER_FORGET] = instr_forget_exec,
 	[INSTR_EXTERN_OBJ] = instr_extern_obj_exec,
 	[INSTR_EXTERN_FUNC] = instr_extern_func_exec,
@@ -8546,7 +8622,8 @@ 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)
+			      uint32_t *timeout,
+			      uint32_t n_timeouts)
 {
 	struct learner *l = NULL;
 	struct action *default_action;
@@ -8616,6 +8693,7 @@ rte_swx_pipeline_learner_config(struct rte_swx_pipeline *p,
 	/* Any other checks. */
 	CHECK(size, EINVAL);
 	CHECK(timeout, EINVAL);
+	CHECK(n_timeouts && (n_timeouts < RTE_SWX_TABLE_LEARNER_N_KEY_TIMEOUTS_MAX), EINVAL);
 
 	/* Memory allocation. */
 	l = calloc(1, sizeof(struct learner));
@@ -8702,7 +8780,10 @@ rte_swx_pipeline_learner_config(struct rte_swx_pipeline *p,
 
 	l->size = size;
 
-	l->timeout = timeout;
+	for (i = 0; i < n_timeouts; i++)
+		l->timeout[i] = timeout[i];
+
+	l->n_timeouts = n_timeouts;
 
 	l->id = p->n_learners;
 
@@ -8734,6 +8815,8 @@ learner_params_free(struct rte_swx_table_learner_params *params)
 
 	free(params->key_mask0);
 
+	free(params->key_timeout);
+
 	free(params);
 }
 
@@ -8787,9 +8870,16 @@ learner_params_get(struct learner *l)
 	/* Maximum number of keys. */
 	params->n_keys_max = l->size;
 
+	/* Memory allocation. */
+	params->key_timeout = calloc(l->n_timeouts, sizeof(uint32_t));
+	if (!params->key_timeout)
+		goto error;
+
 	/* Timeout. */
-	params->key_timeout[0] = l->timeout;
-	params->n_key_timeouts = 1;
+	for (i = 0; i < l->n_timeouts; i++)
+		params->key_timeout[i] = l->timeout[i];
+
+	params->n_key_timeouts = l->n_timeouts;
 
 	return params;
 
@@ -10170,6 +10260,7 @@ rte_swx_ctl_pipeline_learner_stats_read(struct rte_swx_pipeline *p,
 	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_rearm = learner_stats->n_pkts_rearm;
 	stats->n_pkts_forget = learner_stats->n_pkts_forget;
 
 	return 0;
@@ -10583,6 +10674,8 @@ instr_type_to_name(struct instruction *instr)
 	case INSTR_LEARNER_AF: return "INSTR_LEARNER_AF";
 
 	case INSTR_LEARNER_LEARN: return "INSTR_LEARNER_LEARN";
+	case INSTR_LEARNER_REARM: return "INSTR_LEARNER_REARM";
+	case INSTR_LEARNER_REARM_NEW: return "INSTR_LEARNER_REARM_NEW";
 	case INSTR_LEARNER_FORGET: return "INSTR_LEARNER_FORGET";
 
 	case INSTR_EXTERN_OBJ: return "INSTR_EXTERN_OBJ";
@@ -11207,11 +11300,40 @@ instr_learn_export(struct instruction *instr, FILE *f)
 		"\t{\n"
 		"\t\t.type = %s,\n"
 		"\t\t.learn = {\n"
-		"\t\t\t\t.action_id = %u,\n"
+		"\t\t\t.action_id = %u,\n"
+		"\t\t\t.mf_first_arg_offset = %u,\n"
+		"\t\t\t.mf_timeout_id_offset = %u,\n"
+		"\t\t\t.mf_timeout_id_n_bits = %u,\n"
 		"\t\t},\n"
 		"\t},\n",
 		instr_type_to_name(instr),
-		instr->learn.action_id);
+		instr->learn.action_id,
+		instr->learn.mf_first_arg_offset,
+		instr->learn.mf_timeout_id_offset,
+		instr->learn.mf_timeout_id_n_bits);
+}
+
+static void
+instr_rearm_export(struct instruction *instr, FILE *f)
+{
+	if (instr->type == INSTR_LEARNER_REARM)
+		fprintf(f,
+			"\t{\n"
+			"\t\t.type = %s,\n"
+			"\t},\n",
+			instr_type_to_name(instr));
+	else
+		fprintf(f,
+			"\t{\n"
+			"\t\t.type = %s,\n"
+			"\t\t.learn = {\n"
+			"\t\t\t.mf_timeout_id_offset = %u,\n"
+			"\t\t\t.mf_timeout_id_n_bits = %u,\n"
+			"\t\t},\n"
+			"\t},\n",
+			instr_type_to_name(instr),
+			instr->learn.mf_timeout_id_offset,
+			instr->learn.mf_timeout_id_n_bits);
 }
 
 static void
@@ -11509,6 +11631,8 @@ static instruction_export_t export_table[] = {
 	[INSTR_LEARNER_AF] = instr_table_export,
 
 	[INSTR_LEARNER_LEARN] = instr_learn_export,
+	[INSTR_LEARNER_REARM] = instr_rearm_export,
+	[INSTR_LEARNER_REARM_NEW] = instr_rearm_export,
 	[INSTR_LEARNER_FORGET] = instr_forget_export,
 
 	[INSTR_EXTERN_OBJ] = instr_extern_export,
@@ -11730,6 +11854,8 @@ instr_type_to_func(struct instruction *instr)
 	case INSTR_LEARNER_AF: return NULL;
 
 	case INSTR_LEARNER_LEARN: return "__instr_learn_exec";
+	case INSTR_LEARNER_REARM: return "__instr_rearm_exec";
+	case INSTR_LEARNER_REARM_NEW: return "__instr_rearm_new_exec";
 	case INSTR_LEARNER_FORGET: return "__instr_forget_exec";
 
 	case INSTR_EXTERN_OBJ: return NULL;
diff --git a/lib/pipeline/rte_swx_pipeline.h b/lib/pipeline/rte_swx_pipeline.h
index c95d0c7682..a5a0954915 100644
--- a/lib/pipeline/rte_swx_pipeline.h
+++ b/lib/pipeline/rte_swx_pipeline.h
@@ -786,7 +786,9 @@ struct rte_swx_pipeline_learner_params {
  * @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.
+ *   Array of possible table entry timeouts in seconds. Must be non-NULL.
+ * @param[in] n_timeouts
+ *   Number of elements in the *timeout* array.
  * @return
  *   0 on success or the following error codes otherwise:
  *   -EINVAL: Invalid argument;
@@ -800,7 +802,8 @@ 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);
+				uint32_t *timeout,
+				uint32_t n_timeouts);
 
 /**
  * Pipeline register array configure
diff --git a/lib/pipeline/rte_swx_pipeline_internal.h b/lib/pipeline/rte_swx_pipeline_internal.h
index 51bb464f5f..f8a6661f75 100644
--- a/lib/pipeline/rte_swx_pipeline_internal.h
+++ b/lib/pipeline/rte_swx_pipeline_internal.h
@@ -476,9 +476,13 @@ enum instruction_type {
 	INSTR_LEARNER,
 	INSTR_LEARNER_AF,
 
-	/* learn LEARNER ACTION_NAME [ m.action_first_arg ] */
+	/* learn ACTION_NAME [ m.action_first_arg ] m.timeout_id */
 	INSTR_LEARNER_LEARN,
 
+	/* rearm [ m.timeout_id ] */
+	INSTR_LEARNER_REARM,
+	INSTR_LEARNER_REARM_NEW,
+
 	/* forget */
 	INSTR_LEARNER_FORGET,
 
@@ -611,7 +615,9 @@ struct instr_table {
 
 struct instr_learn {
 	uint8_t action_id;
-	uint8_t mf_offset;
+	uint8_t mf_first_arg_offset;
+	uint8_t mf_timeout_id_offset;
+	uint8_t mf_timeout_id_n_bits;
 };
 
 struct instr_extern_obj {
@@ -850,7 +856,8 @@ struct learner {
 	int *action_is_for_default_entry;
 
 	uint32_t size;
-	uint32_t timeout;
+	uint32_t timeout[RTE_SWX_TABLE_LEARNER_N_KEY_TIMEOUTS_MAX];
+	uint32_t n_timeouts;
 	uint32_t id;
 };
 
@@ -864,6 +871,7 @@ struct learner_runtime {
 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_rearm;
 	uint64_t n_pkts_forget;
 	uint64_t *n_pkts_action;
 };
@@ -2202,7 +2210,9 @@ __instr_learn_exec(struct rte_swx_pipeline *p,
 		   const struct instruction *ip)
 {
 	uint64_t action_id = ip->learn.action_id;
-	uint32_t mf_offset = ip->learn.mf_offset;
+	uint32_t mf_first_arg_offset = ip->learn.mf_first_arg_offset;
+	uint32_t timeout_id = METADATA_READ(t, ip->learn.mf_timeout_id_offset,
+		ip->learn.mf_timeout_id_n_bits);
 	uint32_t learner_id = t->learner_id;
 	struct rte_swx_table_state *ts = &t->table_state[p->n_tables +
 		p->n_selectors + learner_id];
@@ -2215,8 +2225,8 @@ __instr_learn_exec(struct rte_swx_pipeline *p,
 					   l->mailbox,
 					   t->time,
 					   action_id,
-					   &t->metadata[mf_offset],
-					   0);
+					   &t->metadata[mf_first_arg_offset],
+					   timeout_id);
 
 	TRACE("[Thread %2u] learner %u learn %s\n",
 	      p->thread_id,
@@ -2226,6 +2236,54 @@ __instr_learn_exec(struct rte_swx_pipeline *p,
 	stats->n_pkts_learn[status] += 1;
 }
 
+/*
+ * rearm.
+ */
+static inline void
+__instr_rearm_exec(struct rte_swx_pipeline *p,
+		   struct thread *t,
+		   const struct instruction *ip __rte_unused)
+{
+	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_rearm(ts->obj, l->mailbox, t->time);
+
+	TRACE("[Thread %2u] learner %u rearm\n",
+	      p->thread_id,
+	      learner_id);
+
+	stats->n_pkts_rearm += 1;
+}
+
+static inline void
+__instr_rearm_new_exec(struct rte_swx_pipeline *p,
+		       struct thread *t,
+		       const struct instruction *ip)
+{
+	uint32_t timeout_id = METADATA_READ(t, ip->learn.mf_timeout_id_offset,
+		ip->learn.mf_timeout_id_n_bits);
+	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_rearm_new(ts->obj, l->mailbox, t->time, timeout_id);
+
+	TRACE("[Thread %2u] learner %u rearm with timeout ID %u\n",
+	      p->thread_id,
+	      learner_id,
+	      timeout_id);
+
+	stats->n_pkts_rearm += 1;
+}
+
 /*
  * forget.
  */
diff --git a/lib/pipeline/rte_swx_pipeline_spec.c b/lib/pipeline/rte_swx_pipeline_spec.c
index b2f2469b18..904b9eb471 100644
--- a/lib/pipeline/rte_swx_pipeline_spec.c
+++ b/lib/pipeline/rte_swx_pipeline_spec.c
@@ -29,7 +29,8 @@
 #define LEARNER_BLOCK 7
 #define LEARNER_KEY_BLOCK 8
 #define LEARNER_ACTIONS_BLOCK 9
-#define APPLY_BLOCK 10
+#define LEARNER_TIMEOUT_BLOCK 10
+#define APPLY_BLOCK 11
 
 /*
  * extobj.
@@ -1395,14 +1396,18 @@ selector_block_parse(struct selector_spec *s,
  *	}
  *	default_action ACTION_NAME args none | ARG0_NAME ARG0_VALUE ... [ const ]
  *	size SIZE
- *	timeout TIMEOUT_IN_SECONDS
+ *	timeout {
+ *		TIMEOUT_IN_SECONDS
+ *		...
+ *	}
  * }
  */
 struct learner_spec {
 	char *name;
 	struct rte_swx_pipeline_learner_params params;
 	uint32_t size;
-	uint32_t timeout;
+	uint32_t *timeout;
+	uint32_t n_timeouts;
 };
 
 static void
@@ -1457,7 +1462,10 @@ learner_spec_free(struct learner_spec *s)
 
 	s->size = 0;
 
-	s->timeout = 0;
+	free(s->timeout);
+	s->timeout = NULL;
+
+	s->n_timeouts = 0;
 }
 
 static int
@@ -1719,6 +1727,95 @@ learner_default_action_statement_parse(struct learner_spec *s,
 	return status;
 }
 
+static int
+learner_timeout_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 timeout statement.";
+		return -EINVAL;
+	}
+
+	/* block_mask. */
+	*block_mask |= 1 << LEARNER_TIMEOUT_BLOCK;
+
+	return 0;
+}
+
+static int
+learner_timeout_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)
+{
+	uint32_t *new_timeout = NULL;
+	char *str;
+	uint32_t val;
+	int status = 0;
+
+	/* Handle end of block. */
+	if ((n_tokens == 1) && !strcmp(tokens[0], "}")) {
+		*block_mask &= ~(1 << LEARNER_TIMEOUT_BLOCK);
+		return 0;
+	}
+
+	/* Check input arguments. */
+	if (n_tokens != 1) {
+		status = -EINVAL;
+		goto error;
+	}
+
+	str = tokens[0];
+	val = strtoul(str, &str, 0);
+	if (str[0]) {
+		status = -EINVAL;
+		goto error;
+	}
+
+	new_timeout = realloc(s->timeout, (s->n_timeouts + 1) * sizeof(uint32_t));
+	if (!new_timeout) {
+		status = -ENOMEM;
+		goto error;
+	}
+
+	s->timeout = new_timeout;
+	s->timeout[s->n_timeouts] = val;
+	s->n_timeouts++;
+
+	return 0;
+
+error:
+	free(new_timeout);
+
+	if (err_line)
+		*err_line = n_lines;
+
+	if (err_msg)
+		switch (status) {
+		case -ENOMEM:
+			*err_msg = "Memory allocation failed.";
+			break;
+
+		default:
+			*err_msg = "Invalid timeout value statement.";
+			break;
+		}
+
+	return status;
+}
+
+
 static int
 learner_statement_parse(struct learner_spec *s,
 		      uint32_t *block_mask,
@@ -1780,6 +1877,15 @@ learner_block_parse(struct learner_spec *s,
 						   err_line,
 						   err_msg);
 
+	if (*block_mask & (1 << LEARNER_TIMEOUT_BLOCK))
+		return learner_timeout_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);
@@ -1833,28 +1939,13 @@ learner_block_parse(struct learner_spec *s,
 		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;
-	}
+	if (!strcmp(tokens[0], "timeout"))
+		return learner_timeout_statement_parse(block_mask,
+						       tokens,
+						       n_tokens,
+						       n_lines,
+						       err_line,
+						       err_msg);
 
 	/* Anything else. */
 	if (err_line)
@@ -2365,7 +2456,8 @@ rte_swx_pipeline_build_from_spec(struct rte_swx_pipeline *p,
 				learner_spec.name,
 				&learner_spec.params,
 				learner_spec.size,
-				learner_spec.timeout);
+				learner_spec.timeout,
+				learner_spec.n_timeouts);
 			if (status) {
 				if (err_line)
 					*err_line = n_lines;
-- 
2.17.1


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH V2 3/3] examples/pipeline: improve learner table timers
  2022-04-22 13:03 ` [PATCH V2 1/3] table: " Cristian Dumitrescu
  2022-04-22 13:03   ` [PATCH V2 2/3] pipeline: " Cristian Dumitrescu
@ 2022-04-22 13:03   ` Cristian Dumitrescu
  2022-05-20 22:12   ` [PATCH V3 1/3] table: " Cristian Dumitrescu
  2 siblings, 0 replies; 10+ messages in thread
From: Cristian Dumitrescu @ 2022-04-22 13:03 UTC (permalink / raw)
  To: dev

Added the rearm counter to the statistics. Updated the learner table
example to the new learner table timer operation.

Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---
 examples/pipeline/cli.c                 |  2 ++
 examples/pipeline/examples/learner.spec | 15 +++++++++++++--
 2 files changed, 15 insertions(+), 2 deletions(-)

diff --git a/examples/pipeline/cli.c b/examples/pipeline/cli.c
index d52ad6b61e..0334616bd9 100644
--- a/examples/pipeline/cli.c
+++ b/examples/pipeline/cli.c
@@ -2677,12 +2677,14 @@ cmd_pipeline_stats(char **tokens,
 			"\t\tMiss (packets): %" PRIu64 "\n"
 			"\t\tLearn OK (packets): %" PRIu64 "\n"
 			"\t\tLearn error (packets): %" PRIu64 "\n"
+			"\t\tRearm (packets): %" PRIu64 "\n"
 			"\t\tForget (packets): %" PRIu64 "\n",
 			learner_info.name,
 			stats.n_pkts_hit,
 			stats.n_pkts_miss,
 			stats.n_pkts_learn_ok,
 			stats.n_pkts_learn_err,
+			stats.n_pkts_rearm,
 			stats.n_pkts_forget);
 		out_size -= strlen(out);
 		out += strlen(out);
diff --git a/examples/pipeline/examples/learner.spec b/examples/pipeline/examples/learner.spec
index 4ee52da7ac..095325c293 100644
--- a/examples/pipeline/examples/learner.spec
+++ b/examples/pipeline/examples/learner.spec
@@ -48,6 +48,9 @@ struct metadata_t {
 	bit<32> port_in
 	bit<32> port_out
 
+	// Key timeout.
+	bit<32> timeout_id
+
 	// Arguments for the "fwd_action" action.
 	bit<32> fwd_action_arg_port_out
 }
@@ -68,10 +71,14 @@ struct fwd_action_args_t {
 
 action fwd_action args instanceof fwd_action_args_t {
 	mov m.port_out t.port_out
+	rearm
 	return
 }
 
 action learn_action args none {
+	// Pick the key timeout. Timeout ID #1 (i.e. 120 seconds) is selected.
+	mov m.timeout_id 1
+
 	// Read current counter value into m.fwd_action_arg_port_out.
 	regrd m.fwd_action_arg_port_out counter 0
 
@@ -84,7 +91,7 @@ action learn_action args none {
 	// Add the current lookup key to the table with fwd_action as the key action. The action
 	// arguments are read from the packet meta-data (the m.fwd_action_arg_port_out field). These
 	// packet meta-data fields have to be written before the "learn" instruction is invoked.
-	learn fwd_action m.fwd_action_arg_port_out
+	learn fwd_action m.fwd_action_arg_port_out m.timeout_id
 
 	// Send the current packet to the same output port.
 	mov m.port_out m.fwd_action_arg_port_out
@@ -110,7 +117,11 @@ learner fwd_table {
 
 	size 1048576
 
-	timeout 120
+	timeout {
+		60
+		120
+		180
+	}
 }
 
 //
-- 
2.17.1


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH V3 1/3] table: improve learner table timers
  2022-04-22 13:03 ` [PATCH V2 1/3] table: " Cristian Dumitrescu
  2022-04-22 13:03   ` [PATCH V2 2/3] pipeline: " Cristian Dumitrescu
  2022-04-22 13:03   ` [PATCH V2 3/3] examples/pipeline: " Cristian Dumitrescu
@ 2022-05-20 22:12   ` Cristian Dumitrescu
  2022-05-20 22:12     ` [PATCH V3 2/3] pipeline: " Cristian Dumitrescu
                       ` (2 more replies)
  2 siblings, 3 replies; 10+ messages in thread
From: Cristian Dumitrescu @ 2022-05-20 22:12 UTC (permalink / raw)
  To: dev

Previously, on lookup hit, the hit key had its timer automatically
rearmed with the same timeout in order to prevent its expiration. Now,
a broader set of actions is available on lookup hit, which has to be
managed explicitly: the key can have its timer rearmed with the same
or with a different timeout, or the key timer can be left unmodified.
The latter option allows the key to expire naturally when the timer
eventually runs out, unless the key is hit again and its timer rearmed
at that point. Needed by the TCP connection tracking state machine.

Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---
Depends-on: series-22386 ("[V5,1/6] port: support packet mirroring")
Depends-on: patch-22480 ("[V4] pipeline: support default action arguments")

Change log:

V3:
-Added timer update function.
-Added key copy with mask support.

 lib/pipeline/rte_swx_pipeline.c          |   3 +-
 lib/pipeline/rte_swx_pipeline_internal.h |   3 +-
 lib/table/rte_swx_table_learner.c        | 152 ++++++++++++++++++++---
 lib/table/rte_swx_table_learner.h        | 109 +++++++++++++++-
 lib/table/version.map                    |   5 +
 5 files changed, 251 insertions(+), 21 deletions(-)

diff --git a/lib/pipeline/rte_swx_pipeline.c b/lib/pipeline/rte_swx_pipeline.c
index dfbac929c7..17be31d5a4 100644
--- a/lib/pipeline/rte_swx_pipeline.c
+++ b/lib/pipeline/rte_swx_pipeline.c
@@ -8788,7 +8788,8 @@ learner_params_get(struct learner *l)
 	params->n_keys_max = l->size;
 
 	/* Timeout. */
-	params->key_timeout = l->timeout;
+	params->key_timeout[0] = l->timeout;
+	params->n_key_timeouts = 1;
 
 	return params;
 
diff --git a/lib/pipeline/rte_swx_pipeline_internal.h b/lib/pipeline/rte_swx_pipeline_internal.h
index 381a35c6e0..51bb464f5f 100644
--- a/lib/pipeline/rte_swx_pipeline_internal.h
+++ b/lib/pipeline/rte_swx_pipeline_internal.h
@@ -2215,7 +2215,8 @@ __instr_learn_exec(struct rte_swx_pipeline *p,
 					   l->mailbox,
 					   t->time,
 					   action_id,
-					   &t->metadata[mf_offset]);
+					   &t->metadata[mf_offset],
+					   0);
 
 	TRACE("[Thread %2u] learner %u learn %s\n",
 	      p->thread_id,
diff --git a/lib/table/rte_swx_table_learner.c b/lib/table/rte_swx_table_learner.c
index 15576c2aa3..222acfc962 100644
--- a/lib/table/rte_swx_table_learner.c
+++ b/lib/table/rte_swx_table_learner.c
@@ -157,6 +157,17 @@ hash(void *key, void *key_mask, uint32_t key_size, uint32_t seed)
 	}
 }
 
+/* n_bytes needs to be a multiple of 8 bytes. */
+static void
+table_keycpy(void *dst, void *src, void *src_mask, uint32_t n_bytes)
+{
+	uint64_t *dst64 = dst, *src64 = src, *src_mask64 = src_mask;
+	uint32_t i;
+
+	for (i = 0; i < n_bytes / sizeof(uint64_t); i++)
+		dst64[i] = src64[i] & src_mask64[i];
+}
+
 /*
  * Return: 0 = Keys are NOT equal; 1 = Keys are equal.
  */
@@ -230,12 +241,16 @@ table_keycmp(void *a, void *b, void *b_mask, uint32_t n_bytes)
 
 #define TABLE_KEYS_PER_BUCKET 4
 
+#define TABLE_BUCKET_USEFUL_SIZE \
+	(TABLE_KEYS_PER_BUCKET * (sizeof(uint32_t) + sizeof(uint32_t) + sizeof(uint8_t)))
+
 #define TABLE_BUCKET_PAD_SIZE \
-	(RTE_CACHE_LINE_SIZE - TABLE_KEYS_PER_BUCKET * (sizeof(uint32_t) + sizeof(uint32_t)))
+	(RTE_CACHE_LINE_SIZE - TABLE_BUCKET_USEFUL_SIZE)
 
 struct table_bucket {
 	uint32_t time[TABLE_KEYS_PER_BUCKET];
 	uint32_t sig[TABLE_KEYS_PER_BUCKET];
+	uint8_t key_timeout_id[TABLE_KEYS_PER_BUCKET];
 	uint8_t pad[TABLE_BUCKET_PAD_SIZE];
 	uint8_t key[0];
 };
@@ -284,8 +299,11 @@ struct table_params {
 	/* log2(bucket_size). Purpose: avoid multiplication with non-power of 2 numbers. */
 	size_t bucket_size_log2;
 
-	/* Timeout in CPU clock cycles. */
-	uint64_t key_timeout;
+	/* Set of all possible key timeout values measured in CPU clock cycles. */
+	uint64_t key_timeout[RTE_SWX_TABLE_LEARNER_N_KEY_TIMEOUTS_MAX];
+
+	/* Number of key timeout values. */
+	uint32_t n_key_timeouts;
 
 	/* Total memory size. */
 	size_t total_size;
@@ -302,18 +320,42 @@ struct table {
 	uint8_t buckets[0];
 } __rte_cache_aligned;
 
+/* The timeout (in cycles) is stored in the table as a 32-bit value by truncating its least
+ * significant 32 bits. Therefore, to make sure the time is always advancing when adding the timeout
+ * value on top of the current time, the minimum timeout value is 1^32 cycles, which is 2 seconds on
+ * a 2 GHz CPU.
+ */
+static uint64_t
+timeout_convert(uint32_t timeout_in_seconds)
+{
+	uint64_t timeout_in_cycles = timeout_in_seconds * rte_get_tsc_hz();
+
+	if (!(timeout_in_cycles >> 32))
+		timeout_in_cycles = 1LLU << 32;
+
+	return timeout_in_cycles;
+}
+
 static int
 table_params_get(struct table_params *p, struct rte_swx_table_learner_params *params)
 {
+	uint32_t i;
+
 	/* Check input parameters. */
 	if (!params ||
 	    !params->key_size ||
 	    (params->key_size > 64) ||
 	    !params->n_keys_max ||
 	    (params->n_keys_max > 1U << 31) ||
-	    !params->key_timeout)
+	    !params->key_timeout ||
+	    !params->n_key_timeouts ||
+	    (params->n_key_timeouts > RTE_SWX_TABLE_LEARNER_N_KEY_TIMEOUTS_MAX))
 		return -EINVAL;
 
+	for (i = 0; i < params->n_key_timeouts; i++)
+		if (!params->key_timeout[i])
+			return -EINVAL;
+
 	/* Key. */
 	p->key_size = params->key_size;
 
@@ -346,7 +388,13 @@ table_params_get(struct table_params *p, struct rte_swx_table_learner_params *pa
 	p->bucket_size_log2 = __builtin_ctzll(p->bucket_size);
 
 	/* Timeout. */
-	p->key_timeout = params->key_timeout * rte_get_tsc_hz();
+	for (i = 0; i < params->n_key_timeouts; i++)
+		p->key_timeout[i] = timeout_convert(params->key_timeout[i]);
+
+	p->n_key_timeouts = rte_align32pow2(params->n_key_timeouts);
+
+	for ( ; i < p->n_key_timeouts; i++)
+		p->key_timeout[i] = p->key_timeout[0];
 
 	/* Total size. */
 	p->total_size = sizeof(struct table) + p->n_buckets * p->bucket_size;
@@ -423,6 +471,23 @@ rte_swx_table_learner_free(void *table)
 	env_free(t, t->params.total_size);
 }
 
+int
+rte_swx_table_learner_timeout_update(void *table,
+				     uint32_t key_timeout_id,
+				     uint32_t key_timeout)
+{
+	struct table *t = table;
+
+	if (!t ||
+	    (key_timeout_id >= t->params.n_key_timeouts) ||
+	    !key_timeout)
+		return -EINVAL;
+
+	t->params.key_timeout[key_timeout_id] = timeout_convert(key_timeout);
+
+	return 0;
+}
+
 struct mailbox {
 	/* Writer: lookup state 0. Reader(s): lookup state 1, add(). */
 	struct table_bucket *bucket;
@@ -505,8 +570,6 @@ rte_swx_table_learner_lookup(void *table,
 				/* Hit. */
 				rte_prefetch0(data);
 
-				b->time[i] = (input_time + t->params.key_timeout) >> 32;
-
 				m->hit = 1;
 				m->bucket_key_pos = i;
 				m->state = 0;
@@ -536,23 +599,83 @@ rte_swx_table_learner_lookup(void *table,
 	}
 }
 
+void
+rte_swx_table_learner_rearm(void *table,
+			    void *mailbox,
+			    uint64_t input_time)
+{
+	struct table *t = table;
+	struct mailbox *m = mailbox;
+	struct table_bucket *b;
+	size_t bucket_key_pos;
+	uint64_t key_timeout;
+	uint32_t key_timeout_id;
+
+	if (!m->hit)
+		return;
+
+	b = m->bucket;
+	bucket_key_pos = m->bucket_key_pos;
+
+	key_timeout_id = b->key_timeout_id[bucket_key_pos];
+	key_timeout = t->params.key_timeout[key_timeout_id];
+	b->time[bucket_key_pos] = (input_time + key_timeout) >> 32;
+}
+
+void
+rte_swx_table_learner_rearm_new(void *table,
+				void *mailbox,
+				uint64_t input_time,
+				uint32_t key_timeout_id)
+{
+	struct table *t = table;
+	struct mailbox *m = mailbox;
+	struct table_bucket *b;
+	size_t bucket_key_pos;
+	uint64_t key_timeout;
+
+	if (!m->hit)
+		return;
+
+	b = m->bucket;
+	bucket_key_pos = m->bucket_key_pos;
+
+	key_timeout_id &= t->params.n_key_timeouts - 1;
+	key_timeout = t->params.key_timeout[key_timeout_id];
+	b->time[bucket_key_pos] = (input_time + key_timeout) >> 32;
+	b->key_timeout_id[bucket_key_pos] = (uint8_t)key_timeout_id;
+}
+
 uint32_t
 rte_swx_table_learner_add(void *table,
 			  void *mailbox,
 			  uint64_t input_time,
 			  uint64_t action_id,
-			  uint8_t *action_data)
+			  uint8_t *action_data,
+			  uint32_t key_timeout_id)
 {
 	struct table *t = table;
 	struct mailbox *m = mailbox;
 	struct table_bucket *b = m->bucket;
+	uint64_t key_timeout;
 	uint32_t i;
 
-	/* Lookup hit: The key, key signature and key time are already properly configured (the key
-	 * time was bumped by lookup), only the key data need to be updated.
+	/* Adjust the key timeout ID to fit the valid range. */
+	key_timeout_id &= t->params.n_key_timeouts - 1;
+	key_timeout = t->params.key_timeout[key_timeout_id];
+
+	/* Lookup hit: The following bucket fields need to be updated:
+	 * - key (key, sig): NO (already correctly set).
+	 * - key timeout (key_timeout_id, time): YES.
+	 * - key data (data): YES.
 	 */
 	if (m->hit) {
-		uint64_t *data = table_bucket_data_get(t, b, m->bucket_key_pos);
+		size_t bucket_key_pos = m->bucket_key_pos;
+		uint64_t *data = table_bucket_data_get(t, b, bucket_key_pos);
+
+		/* Install the key timeout. */
+		b->time[bucket_key_pos] = (input_time + key_timeout) >> 32;
+		b->key_timeout_id[bucket_key_pos] = (uint8_t)key_timeout_id;
 
 		/* Install the key data. */
 		data[0] = action_id;
@@ -576,10 +699,11 @@ rte_swx_table_learner_add(void *table,
 			uint8_t *key = table_bucket_key_get(t, b, i);
 			uint64_t *data = table_bucket_data_get(t, b, i);
 
-			/* Install the key. */
-			b->time[i] = (input_time + t->params.key_timeout) >> 32;
+			/* Install the key and the key timeout. */
+			b->time[i] = (input_time + key_timeout) >> 32;
 			b->sig[i] = m->input_sig;
-			memcpy(key, m->input_key, t->params.key_size);
+			b->key_timeout_id[i] = (uint8_t)key_timeout_id;
+			table_keycpy(key, m->input_key, t->key_mask0, t->params.key_size_pow2);
 
 			/* Install the key data. */
 			data[0] = action_id;
diff --git a/lib/table/rte_swx_table_learner.h b/lib/table/rte_swx_table_learner.h
index eb9d7689fd..1fee306806 100644
--- a/lib/table/rte_swx_table_learner.h
+++ b/lib/table/rte_swx_table_learner.h
@@ -18,13 +18,43 @@ extern "C" {
  * implementation of the "add on miss" scenario: whenever the lookup key is not found in the table
  * (lookup miss), the data plane can decide to add this key to the table with a given action with no
  * control plane intervention. Likewise, the table keys expire based on a configurable timeout and
- * are automatically deleted from the table with no control plane intervention.
+ * are thus automatically removed from the table with no control plane intervention.
+ *
+ * The keys are not automatically rearmed on lookup hit. To delay the key expiration, the key timer
+ * has to be explicitly reinitialized on lookup hit. The key will be kept in the table as long as it
+ * is frequently hit and explicitly rearmed on every hit.
+ *
+ * Operation overview:
+ * 1) Lookup miss:
+ *      a) add: Add the current input key (the key that missed the lookup) to the table with given
+ *              action, action parameters and expiration timeout. This is the way to populate the
+ *              table (which is empty initially). Data plane operation.
+ *      b) Do nothing: Keep the current input key out of the table.
+ * 2) Lookup hit:
+ *      a) add: Update the action, action parameters and/or the expiration timeout for the current
+ *              input key, which is already in the table. The expiration timer of the key is
+ *              automatically rearmed. Data plane operation.
+ *      b) rearm: Rearm the expiration timer for the current input key, which is already in the
+ *              table. The timeout value used for the expiration timer is either the same as the one
+ *              currently associated with the key or a new one can be provided as input. Data plane
+ *              operation.
+ *      c) delete: Delete the current input key from the table. The purpose of this operation is to
+ *              force the deletion of the key from the table before the key expires on timeout due
+ *              to inactivity. Data plane operation.
+ *      d) Do nothing: Keep the expiration timer of the current input key running down. This key
+ *              will thus expire naturally, unless it is hit again as part of a subsequent lookup
+ *              operation, when the key timer can be rearmed or re-added to prolong its life.
  */
 
 #include <stdint.h>
 
 #include <rte_compat.h>
 
+/** Maximum number of key timeout values per learner table. */
+#ifndef RTE_SWX_TABLE_LEARNER_N_KEY_TIMEOUTS_MAX
+#define RTE_SWX_TABLE_LEARNER_N_KEY_TIMEOUTS_MAX 16
+#endif
+
 /** Learner table creation parameters. */
 struct rte_swx_table_learner_params {
 	/** Key size in bytes. Must be non-zero. */
@@ -50,10 +80,16 @@ struct rte_swx_table_learner_params {
 	/** Maximum number of keys to be stored in the table together with their associated data. */
 	uint32_t n_keys_max;
 
-	/** Key timeout in seconds. Must be non-zero. Each table key expires and is automatically
-	 * deleted from the table after this many seconds.
+	/** The set of all possible key timeout values measured in seconds. Each value must be
+	 * non-zero. Each table key expires and is automatically deleted from the table after
+	 * this many seconds.
+	 */
+	uint32_t *key_timeout;
+
+	/** Number of possible key timeout values present in the *key_timeout* set. It must be less
+	 * than or equal to *RTE_SWX_TABLE_LEARNER_N_KEY_TIMEOUTS_MAX*.
 	 */
-	uint32_t key_timeout;
+	uint32_t n_key_timeouts;
 };
 
 /**
@@ -96,6 +132,25 @@ __rte_experimental
 void *
 rte_swx_table_learner_create(struct rte_swx_table_learner_params *params, int numa_node);
 
+/**
+ * Learner table key timeout update
+ *
+ * @param[in] table
+ *   Table handle.
+ * @param[in] key_timeout_id
+ *   Key timeout ID. Must be less than the configured *n_key_timeouts* value.
+ * @param[in] key_timeout
+ *   Key timeout value measured in seconds.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument(s).
+ */
+__rte_experimental
+int
+rte_swx_table_learner_timeout_update(void *table,
+				     uint32_t key_timeout_id,
+				     uint32_t key_timeout);
+
 /**
  * Learner table key lookup
  *
@@ -160,6 +215,8 @@ rte_swx_table_learner_lookup(void *table,
  *   ID of the action associated with the key.
  * @param[out] action_data
  *   Action data for the *action_id* action.
+ * @param[in] key_timeout_id
+ *   Key timeout ID.
  * @return
  *   0 on success, 1 or error (table full).
  */
@@ -169,7 +226,49 @@ rte_swx_table_learner_add(void *table,
 			  void *mailbox,
 			  uint64_t time,
 			  uint64_t action_id,
-			  uint8_t *action_data);
+			  uint8_t *action_data,
+			  uint32_t key_timeout_id);
+
+/**
+ * Learner table key rearm with same timeout value
+ *
+ * This operation takes the latest key that was looked up in the table and, in case of lookup hit,
+ * it rearms its expiration timer using the same timeout value currently associated with the key.
+ *
+ * @param[in] table
+ *   Table handle.
+ * @param[in] mailbox
+ *   Mailbox for the current operation.
+ * @param[in] time
+ *   Current time measured in CPU clock cycles.
+ */
+__rte_experimental
+void
+rte_swx_table_learner_rearm(void *table,
+			    void *mailbox,
+			    uint64_t time);
+
+/**
+ * Learner table key rearm with given timeout value
+ *
+ * This operation takes the latest key that was looked up in the table and, in case of lookup hit,
+ * it rearms its expiration timer using the given timeout value.
+ *
+ * @param[in] table
+ *   Table handle.
+ * @param[in] mailbox
+ *   Mailbox for the current operation.
+ * @param[in] time
+ *   Current time measured in CPU clock cycles.
+ * @param[in] key_timeout_id
+ *   Key timeout ID.
+ */
+__rte_experimental
+void
+rte_swx_table_learner_rearm_new(void *table,
+				void *mailbox,
+				uint64_t time,
+				uint32_t key_timeout_id);
 
 /**
  * Learner table key delete
diff --git a/lib/table/version.map b/lib/table/version.map
index efe5f6e52c..623709f886 100644
--- a/lib/table/version.map
+++ b/lib/table/version.map
@@ -45,4 +45,9 @@ EXPERIMENTAL {
 	rte_swx_table_learner_free;
 	rte_swx_table_learner_lookup;
 	rte_swx_table_learner_mailbox_size_get;
+
+	#added in 22.07
+	rte_swx_table_learner_rearm;
+	rte_swx_table_learner_rearm_new;
+	rte_swx_table_learner_timeout_update;
 };
-- 
2.17.1


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH V3 2/3] pipeline: improve learner table timers
  2022-05-20 22:12   ` [PATCH V3 1/3] table: " Cristian Dumitrescu
@ 2022-05-20 22:12     ` Cristian Dumitrescu
  2022-05-20 22:12     ` [PATCH V3 3/3] examples/pipeline: " Cristian Dumitrescu
  2022-06-01 13:57     ` [PATCH V3 1/3] table: " Thomas Monjalon
  2 siblings, 0 replies; 10+ messages in thread
From: Cristian Dumitrescu @ 2022-05-20 22:12 UTC (permalink / raw)
  To: dev

Enable the pipeline to use the improved learner table timer operation
through the new "rearm" instruction.

Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---
 lib/pipeline/rte_swx_ctl.h               |  50 ++++++
 lib/pipeline/rte_swx_pipeline.c          | 217 ++++++++++++++++++++---
 lib/pipeline/rte_swx_pipeline.h          |   7 +-
 lib/pipeline/rte_swx_pipeline_internal.h |  70 +++++++-
 lib/pipeline/rte_swx_pipeline_spec.c     | 146 ++++++++++++---
 lib/pipeline/version.map                 |   4 +
 6 files changed, 439 insertions(+), 55 deletions(-)

diff --git a/lib/pipeline/rte_swx_ctl.h b/lib/pipeline/rte_swx_ctl.h
index 204026dc0e..689f45e3e7 100644
--- a/lib/pipeline/rte_swx_ctl.h
+++ b/lib/pipeline/rte_swx_ctl.h
@@ -548,6 +548,9 @@ struct rte_swx_ctl_learner_info {
 
 	/** Learner table size parameter. */
 	uint32_t size;
+
+	/** Number of possible key timeout values. */
+	uint32_t n_key_timeouts;
 };
 
 /**
@@ -615,6 +618,50 @@ rte_swx_ctl_learner_action_info_get(struct rte_swx_pipeline *p,
 				    uint32_t learner_action_id,
 				    struct rte_swx_ctl_table_action_info *learner_action);
 
+/**
+ * Learner table timeout get
+ *
+ * @param[in] p
+ *   Pipeline handle.
+ * @param[in] learner_id
+ *   Learner table ID (0 .. *n_learners* - 1).
+ * @param[in] timeout_id
+ *   Timeout ID.
+ * @param[out] timeout
+ *   Timeout value measured in seconds. Must be non-NULL.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument.
+ */
+__rte_experimental
+int
+rte_swx_ctl_pipeline_learner_timeout_get(struct rte_swx_pipeline *p,
+					 uint32_t learner_id,
+					 uint32_t timeout_id,
+					 uint32_t *timeout);
+
+/**
+ * Learner table timeout set
+ *
+ * @param[in] p
+ *   Pipeline handle.
+ * @param[in] learner_id
+ *   Learner table ID (0 .. *n_learners* - 1).
+ * @param[in] timeout_id
+ *   Timeout ID.
+ * @param[in] timeout
+ *   Timeout value measured in seconds.
+ * @return
+ *   0 on success or the following error codes otherwise:
+ *   -EINVAL: Invalid argument.
+ */
+__rte_experimental
+int
+rte_swx_ctl_pipeline_learner_timeout_set(struct rte_swx_pipeline *p,
+					 uint32_t learner_id,
+					 uint32_t timeout_id,
+					 uint32_t timeout);
+
 /** Learner table statistics. */
 struct rte_swx_learner_stats {
 	/** Number of packets with lookup hit. */
@@ -629,6 +676,9 @@ struct rte_swx_learner_stats {
 	/** Number of packets with learning error. */
 	uint64_t n_pkts_learn_err;
 
+	/** Number of packets with rearm event. */
+	uint64_t n_pkts_rearm;
+
 	/** Number of packets with forget event. */
 	uint64_t n_pkts_forget;
 
diff --git a/lib/pipeline/rte_swx_pipeline.c b/lib/pipeline/rte_swx_pipeline.c
index 17be31d5a4..0d8c184ae7 100644
--- a/lib/pipeline/rte_swx_pipeline.c
+++ b/lib/pipeline/rte_swx_pipeline.c
@@ -2556,7 +2556,7 @@ instr_learner_af_exec(struct rte_swx_pipeline *p)
 	stats->n_pkts_action[action_id] = n_pkts_action + 1;
 
 	/* Thread. */
-	thread_ip_action_call(p, t, action_id);
+	thread_ip_inc(p);
 
 	/* Action */
 	action_func(p);
@@ -2583,31 +2583,38 @@ instr_learn_translate(struct rte_swx_pipeline *p,
 		      struct instruction_data *data __rte_unused)
 {
 	struct action *a;
-	const char *mf_name;
-	uint32_t mf_offset = 0;
+	struct field *mf_first_arg = NULL, *mf_timeout_id = NULL;
+	const char *mf_first_arg_name, *mf_timeout_id_name;
 
 	CHECK(action, EINVAL);
-	CHECK((n_tokens == 2) || (n_tokens == 3), EINVAL);
+	CHECK((n_tokens == 3) || (n_tokens == 4), EINVAL);
 
+	/* Action. */
 	a = action_find(p, tokens[1]);
 	CHECK(a, EINVAL);
 	CHECK(!action_has_nbo_args(a), EINVAL);
 
-	mf_name = (n_tokens > 2) ? tokens[2] : NULL;
-	CHECK(!learner_action_args_check(p, a, mf_name), EINVAL);
-
-	if (mf_name) {
-		struct field *mf;
-
-		mf = metadata_field_parse(p, mf_name);
-		CHECK(mf, EINVAL);
+	/* Action first argument. */
+	mf_first_arg_name = (n_tokens == 4) ? tokens[2] : NULL;
+	CHECK(!learner_action_args_check(p, a, mf_first_arg_name), EINVAL);
 
-		mf_offset = mf->offset / 8;
+	if (mf_first_arg_name) {
+		mf_first_arg = metadata_field_parse(p, mf_first_arg_name);
+		CHECK(mf_first_arg, EINVAL);
 	}
 
+	/* Timeout ID. */
+	mf_timeout_id_name = (n_tokens == 4) ? tokens[3] : tokens[2];
+	CHECK_NAME(mf_timeout_id_name, EINVAL);
+	mf_timeout_id = metadata_field_parse(p, mf_timeout_id_name);
+	CHECK(mf_timeout_id, EINVAL);
+
+	/* Instruction. */
 	instr->type = INSTR_LEARNER_LEARN;
 	instr->learn.action_id = a->id;
-	instr->learn.mf_offset = mf_offset;
+	instr->learn.mf_first_arg_offset = mf_first_arg ? (mf_first_arg->offset / 8) : 0;
+	instr->learn.mf_timeout_id_offset = mf_timeout_id->offset / 8;
+	instr->learn.mf_timeout_id_n_bits = mf_timeout_id->n_bits;
 
 	return 0;
 }
@@ -2624,6 +2631,66 @@ instr_learn_exec(struct rte_swx_pipeline *p)
 	thread_ip_inc(p);
 }
 
+/*
+ * rearm.
+ */
+static int
+instr_rearm_translate(struct rte_swx_pipeline *p,
+		      struct action *action,
+		      char **tokens,
+		      int n_tokens,
+		      struct instruction *instr,
+		      struct instruction_data *data __rte_unused)
+{
+	struct field *mf_timeout_id;
+	const char *mf_timeout_id_name;
+
+	CHECK(action, EINVAL);
+	CHECK((n_tokens == 1) || (n_tokens == 2), EINVAL);
+
+	/* INSTR_LEARNER_REARM. */
+	if (n_tokens == 1) {
+		instr->type = INSTR_LEARNER_REARM;
+		return 0;
+	}
+
+	/* INSTR_LEARNER_REARM_NEW. */
+	mf_timeout_id_name = tokens[1];
+	CHECK_NAME(mf_timeout_id_name, EINVAL);
+	mf_timeout_id = metadata_field_parse(p, mf_timeout_id_name);
+	CHECK(mf_timeout_id, EINVAL);
+
+	instr->type = INSTR_LEARNER_REARM_NEW;
+	instr->learn.mf_timeout_id_offset = mf_timeout_id->offset / 8;
+	instr->learn.mf_timeout_id_n_bits = mf_timeout_id->n_bits;
+
+	return 0;
+}
+
+static inline void
+instr_rearm_exec(struct rte_swx_pipeline *p)
+{
+	struct thread *t = &p->threads[p->thread_id];
+	struct instruction *ip = t->ip;
+
+	__instr_rearm_exec(p, t, ip);
+
+	/* Thread. */
+	thread_ip_inc(p);
+}
+
+static inline void
+instr_rearm_new_exec(struct rte_swx_pipeline *p)
+{
+	struct thread *t = &p->threads[p->thread_id];
+	struct instruction *ip = t->ip;
+
+	__instr_rearm_new_exec(p, t, ip);
+
+	/* Thread. */
+	thread_ip_inc(p);
+}
+
 /*
  * forget.
  */
@@ -6051,6 +6118,13 @@ instr_translate(struct rte_swx_pipeline *p,
 					     n_tokens - tpos,
 					     instr,
 					     data);
+	if (!strcmp(tokens[tpos], "rearm"))
+		return instr_rearm_translate(p,
+					     action,
+					     &tokens[tpos],
+					     n_tokens - tpos,
+					     instr,
+					     data);
 
 	if (!strcmp(tokens[tpos], "forget"))
 		return instr_forget_translate(p,
@@ -7040,6 +7114,8 @@ static instr_exec_t instruction_table[] = {
 	[INSTR_LEARNER] = instr_learner_exec,
 	[INSTR_LEARNER_AF] = instr_learner_af_exec,
 	[INSTR_LEARNER_LEARN] = instr_learn_exec,
+	[INSTR_LEARNER_REARM] = instr_rearm_exec,
+	[INSTR_LEARNER_REARM_NEW] = instr_rearm_new_exec,
 	[INSTR_LEARNER_FORGET] = instr_forget_exec,
 	[INSTR_EXTERN_OBJ] = instr_extern_obj_exec,
 	[INSTR_EXTERN_FUNC] = instr_extern_func_exec,
@@ -8546,7 +8622,8 @@ 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)
+			      uint32_t *timeout,
+			      uint32_t n_timeouts)
 {
 	struct learner *l = NULL;
 	struct action *default_action;
@@ -8616,6 +8693,7 @@ rte_swx_pipeline_learner_config(struct rte_swx_pipeline *p,
 	/* Any other checks. */
 	CHECK(size, EINVAL);
 	CHECK(timeout, EINVAL);
+	CHECK(n_timeouts && (n_timeouts < RTE_SWX_TABLE_LEARNER_N_KEY_TIMEOUTS_MAX), EINVAL);
 
 	/* Memory allocation. */
 	l = calloc(1, sizeof(struct learner));
@@ -8702,7 +8780,10 @@ rte_swx_pipeline_learner_config(struct rte_swx_pipeline *p,
 
 	l->size = size;
 
-	l->timeout = timeout;
+	for (i = 0; i < n_timeouts; i++)
+		l->timeout[i] = timeout[i];
+
+	l->n_timeouts = n_timeouts;
 
 	l->id = p->n_learners;
 
@@ -8734,6 +8815,8 @@ learner_params_free(struct rte_swx_table_learner_params *params)
 
 	free(params->key_mask0);
 
+	free(params->key_timeout);
+
 	free(params);
 }
 
@@ -8787,9 +8870,16 @@ learner_params_get(struct learner *l)
 	/* Maximum number of keys. */
 	params->n_keys_max = l->size;
 
+	/* Memory allocation. */
+	params->key_timeout = calloc(l->n_timeouts, sizeof(uint32_t));
+	if (!params->key_timeout)
+		goto error;
+
 	/* Timeout. */
-	params->key_timeout[0] = l->timeout;
-	params->n_key_timeouts = 1;
+	for (i = 0; i < l->n_timeouts; i++)
+		params->key_timeout[i] = l->timeout[i];
+
+	params->n_key_timeouts = l->n_timeouts;
 
 	return params;
 
@@ -9984,6 +10074,7 @@ rte_swx_ctl_learner_info_get(struct rte_swx_pipeline *p,
 	learner->n_actions = l->n_actions;
 	learner->default_action_is_const = l->default_action_is_const;
 	learner->size = l->size;
+	learner->n_key_timeouts = l->n_timeouts;
 
 	return 0;
 }
@@ -10039,6 +10130,56 @@ rte_swx_ctl_learner_action_info_get(struct rte_swx_pipeline *p,
 	return 0;
 }
 
+int
+rte_swx_ctl_pipeline_learner_timeout_get(struct rte_swx_pipeline *p,
+					 uint32_t learner_id,
+					 uint32_t timeout_id,
+					 uint32_t *timeout)
+{
+	struct learner *l;
+
+	if (!p || (learner_id >= p->n_learners) || !timeout)
+		return -EINVAL;
+
+	l = learner_find_by_id(p, learner_id);
+	if (!l || (timeout_id >= l->n_timeouts))
+		return -EINVAL;
+
+	*timeout = l->timeout[timeout_id];
+	return 0;
+}
+
+int
+rte_swx_ctl_pipeline_learner_timeout_set(struct rte_swx_pipeline *p,
+					 uint32_t learner_id,
+					 uint32_t timeout_id,
+					 uint32_t timeout)
+{
+	struct learner *l;
+	struct rte_swx_table_state *ts;
+	int status;
+
+	if (!p || (learner_id >= p->n_learners) || !timeout)
+		return -EINVAL;
+
+	l = learner_find_by_id(p, learner_id);
+	if (!l || (timeout_id >= l->n_timeouts))
+		return -EINVAL;
+
+	if (!p->build_done)
+		return -EINVAL;
+
+	ts = &p->table_state[p->n_tables + p->n_selectors + l->id];
+
+	status = rte_swx_table_learner_timeout_update(ts->obj, timeout_id, timeout);
+	if (status)
+		return -EINVAL;
+
+	l->timeout[timeout_id] = timeout;
+
+	return 0;
+}
+
 int
 rte_swx_pipeline_table_state_get(struct rte_swx_pipeline *p,
 				 struct rte_swx_table_state **table_state)
@@ -10170,6 +10311,7 @@ rte_swx_ctl_pipeline_learner_stats_read(struct rte_swx_pipeline *p,
 	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_rearm = learner_stats->n_pkts_rearm;
 	stats->n_pkts_forget = learner_stats->n_pkts_forget;
 
 	return 0;
@@ -10583,6 +10725,8 @@ instr_type_to_name(struct instruction *instr)
 	case INSTR_LEARNER_AF: return "INSTR_LEARNER_AF";
 
 	case INSTR_LEARNER_LEARN: return "INSTR_LEARNER_LEARN";
+	case INSTR_LEARNER_REARM: return "INSTR_LEARNER_REARM";
+	case INSTR_LEARNER_REARM_NEW: return "INSTR_LEARNER_REARM_NEW";
 	case INSTR_LEARNER_FORGET: return "INSTR_LEARNER_FORGET";
 
 	case INSTR_EXTERN_OBJ: return "INSTR_EXTERN_OBJ";
@@ -11207,11 +11351,40 @@ instr_learn_export(struct instruction *instr, FILE *f)
 		"\t{\n"
 		"\t\t.type = %s,\n"
 		"\t\t.learn = {\n"
-		"\t\t\t\t.action_id = %u,\n"
+		"\t\t\t.action_id = %u,\n"
+		"\t\t\t.mf_first_arg_offset = %u,\n"
+		"\t\t\t.mf_timeout_id_offset = %u,\n"
+		"\t\t\t.mf_timeout_id_n_bits = %u,\n"
 		"\t\t},\n"
 		"\t},\n",
 		instr_type_to_name(instr),
-		instr->learn.action_id);
+		instr->learn.action_id,
+		instr->learn.mf_first_arg_offset,
+		instr->learn.mf_timeout_id_offset,
+		instr->learn.mf_timeout_id_n_bits);
+}
+
+static void
+instr_rearm_export(struct instruction *instr, FILE *f)
+{
+	if (instr->type == INSTR_LEARNER_REARM)
+		fprintf(f,
+			"\t{\n"
+			"\t\t.type = %s,\n"
+			"\t},\n",
+			instr_type_to_name(instr));
+	else
+		fprintf(f,
+			"\t{\n"
+			"\t\t.type = %s,\n"
+			"\t\t.learn = {\n"
+			"\t\t\t.mf_timeout_id_offset = %u,\n"
+			"\t\t\t.mf_timeout_id_n_bits = %u,\n"
+			"\t\t},\n"
+			"\t},\n",
+			instr_type_to_name(instr),
+			instr->learn.mf_timeout_id_offset,
+			instr->learn.mf_timeout_id_n_bits);
 }
 
 static void
@@ -11509,6 +11682,8 @@ static instruction_export_t export_table[] = {
 	[INSTR_LEARNER_AF] = instr_table_export,
 
 	[INSTR_LEARNER_LEARN] = instr_learn_export,
+	[INSTR_LEARNER_REARM] = instr_rearm_export,
+	[INSTR_LEARNER_REARM_NEW] = instr_rearm_export,
 	[INSTR_LEARNER_FORGET] = instr_forget_export,
 
 	[INSTR_EXTERN_OBJ] = instr_extern_export,
@@ -11730,6 +11905,8 @@ instr_type_to_func(struct instruction *instr)
 	case INSTR_LEARNER_AF: return NULL;
 
 	case INSTR_LEARNER_LEARN: return "__instr_learn_exec";
+	case INSTR_LEARNER_REARM: return "__instr_rearm_exec";
+	case INSTR_LEARNER_REARM_NEW: return "__instr_rearm_new_exec";
 	case INSTR_LEARNER_FORGET: return "__instr_forget_exec";
 
 	case INSTR_EXTERN_OBJ: return NULL;
diff --git a/lib/pipeline/rte_swx_pipeline.h b/lib/pipeline/rte_swx_pipeline.h
index c95d0c7682..a5a0954915 100644
--- a/lib/pipeline/rte_swx_pipeline.h
+++ b/lib/pipeline/rte_swx_pipeline.h
@@ -786,7 +786,9 @@ struct rte_swx_pipeline_learner_params {
  * @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.
+ *   Array of possible table entry timeouts in seconds. Must be non-NULL.
+ * @param[in] n_timeouts
+ *   Number of elements in the *timeout* array.
  * @return
  *   0 on success or the following error codes otherwise:
  *   -EINVAL: Invalid argument;
@@ -800,7 +802,8 @@ 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);
+				uint32_t *timeout,
+				uint32_t n_timeouts);
 
 /**
  * Pipeline register array configure
diff --git a/lib/pipeline/rte_swx_pipeline_internal.h b/lib/pipeline/rte_swx_pipeline_internal.h
index 51bb464f5f..f8a6661f75 100644
--- a/lib/pipeline/rte_swx_pipeline_internal.h
+++ b/lib/pipeline/rte_swx_pipeline_internal.h
@@ -476,9 +476,13 @@ enum instruction_type {
 	INSTR_LEARNER,
 	INSTR_LEARNER_AF,
 
-	/* learn LEARNER ACTION_NAME [ m.action_first_arg ] */
+	/* learn ACTION_NAME [ m.action_first_arg ] m.timeout_id */
 	INSTR_LEARNER_LEARN,
 
+	/* rearm [ m.timeout_id ] */
+	INSTR_LEARNER_REARM,
+	INSTR_LEARNER_REARM_NEW,
+
 	/* forget */
 	INSTR_LEARNER_FORGET,
 
@@ -611,7 +615,9 @@ struct instr_table {
 
 struct instr_learn {
 	uint8_t action_id;
-	uint8_t mf_offset;
+	uint8_t mf_first_arg_offset;
+	uint8_t mf_timeout_id_offset;
+	uint8_t mf_timeout_id_n_bits;
 };
 
 struct instr_extern_obj {
@@ -850,7 +856,8 @@ struct learner {
 	int *action_is_for_default_entry;
 
 	uint32_t size;
-	uint32_t timeout;
+	uint32_t timeout[RTE_SWX_TABLE_LEARNER_N_KEY_TIMEOUTS_MAX];
+	uint32_t n_timeouts;
 	uint32_t id;
 };
 
@@ -864,6 +871,7 @@ struct learner_runtime {
 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_rearm;
 	uint64_t n_pkts_forget;
 	uint64_t *n_pkts_action;
 };
@@ -2202,7 +2210,9 @@ __instr_learn_exec(struct rte_swx_pipeline *p,
 		   const struct instruction *ip)
 {
 	uint64_t action_id = ip->learn.action_id;
-	uint32_t mf_offset = ip->learn.mf_offset;
+	uint32_t mf_first_arg_offset = ip->learn.mf_first_arg_offset;
+	uint32_t timeout_id = METADATA_READ(t, ip->learn.mf_timeout_id_offset,
+		ip->learn.mf_timeout_id_n_bits);
 	uint32_t learner_id = t->learner_id;
 	struct rte_swx_table_state *ts = &t->table_state[p->n_tables +
 		p->n_selectors + learner_id];
@@ -2215,8 +2225,8 @@ __instr_learn_exec(struct rte_swx_pipeline *p,
 					   l->mailbox,
 					   t->time,
 					   action_id,
-					   &t->metadata[mf_offset],
-					   0);
+					   &t->metadata[mf_first_arg_offset],
+					   timeout_id);
 
 	TRACE("[Thread %2u] learner %u learn %s\n",
 	      p->thread_id,
@@ -2226,6 +2236,54 @@ __instr_learn_exec(struct rte_swx_pipeline *p,
 	stats->n_pkts_learn[status] += 1;
 }
 
+/*
+ * rearm.
+ */
+static inline void
+__instr_rearm_exec(struct rte_swx_pipeline *p,
+		   struct thread *t,
+		   const struct instruction *ip __rte_unused)
+{
+	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_rearm(ts->obj, l->mailbox, t->time);
+
+	TRACE("[Thread %2u] learner %u rearm\n",
+	      p->thread_id,
+	      learner_id);
+
+	stats->n_pkts_rearm += 1;
+}
+
+static inline void
+__instr_rearm_new_exec(struct rte_swx_pipeline *p,
+		       struct thread *t,
+		       const struct instruction *ip)
+{
+	uint32_t timeout_id = METADATA_READ(t, ip->learn.mf_timeout_id_offset,
+		ip->learn.mf_timeout_id_n_bits);
+	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_rearm_new(ts->obj, l->mailbox, t->time, timeout_id);
+
+	TRACE("[Thread %2u] learner %u rearm with timeout ID %u\n",
+	      p->thread_id,
+	      learner_id,
+	      timeout_id);
+
+	stats->n_pkts_rearm += 1;
+}
+
 /*
  * forget.
  */
diff --git a/lib/pipeline/rte_swx_pipeline_spec.c b/lib/pipeline/rte_swx_pipeline_spec.c
index b2f2469b18..904b9eb471 100644
--- a/lib/pipeline/rte_swx_pipeline_spec.c
+++ b/lib/pipeline/rte_swx_pipeline_spec.c
@@ -29,7 +29,8 @@
 #define LEARNER_BLOCK 7
 #define LEARNER_KEY_BLOCK 8
 #define LEARNER_ACTIONS_BLOCK 9
-#define APPLY_BLOCK 10
+#define LEARNER_TIMEOUT_BLOCK 10
+#define APPLY_BLOCK 11
 
 /*
  * extobj.
@@ -1395,14 +1396,18 @@ selector_block_parse(struct selector_spec *s,
  *	}
  *	default_action ACTION_NAME args none | ARG0_NAME ARG0_VALUE ... [ const ]
  *	size SIZE
- *	timeout TIMEOUT_IN_SECONDS
+ *	timeout {
+ *		TIMEOUT_IN_SECONDS
+ *		...
+ *	}
  * }
  */
 struct learner_spec {
 	char *name;
 	struct rte_swx_pipeline_learner_params params;
 	uint32_t size;
-	uint32_t timeout;
+	uint32_t *timeout;
+	uint32_t n_timeouts;
 };
 
 static void
@@ -1457,7 +1462,10 @@ learner_spec_free(struct learner_spec *s)
 
 	s->size = 0;
 
-	s->timeout = 0;
+	free(s->timeout);
+	s->timeout = NULL;
+
+	s->n_timeouts = 0;
 }
 
 static int
@@ -1719,6 +1727,95 @@ learner_default_action_statement_parse(struct learner_spec *s,
 	return status;
 }
 
+static int
+learner_timeout_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 timeout statement.";
+		return -EINVAL;
+	}
+
+	/* block_mask. */
+	*block_mask |= 1 << LEARNER_TIMEOUT_BLOCK;
+
+	return 0;
+}
+
+static int
+learner_timeout_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)
+{
+	uint32_t *new_timeout = NULL;
+	char *str;
+	uint32_t val;
+	int status = 0;
+
+	/* Handle end of block. */
+	if ((n_tokens == 1) && !strcmp(tokens[0], "}")) {
+		*block_mask &= ~(1 << LEARNER_TIMEOUT_BLOCK);
+		return 0;
+	}
+
+	/* Check input arguments. */
+	if (n_tokens != 1) {
+		status = -EINVAL;
+		goto error;
+	}
+
+	str = tokens[0];
+	val = strtoul(str, &str, 0);
+	if (str[0]) {
+		status = -EINVAL;
+		goto error;
+	}
+
+	new_timeout = realloc(s->timeout, (s->n_timeouts + 1) * sizeof(uint32_t));
+	if (!new_timeout) {
+		status = -ENOMEM;
+		goto error;
+	}
+
+	s->timeout = new_timeout;
+	s->timeout[s->n_timeouts] = val;
+	s->n_timeouts++;
+
+	return 0;
+
+error:
+	free(new_timeout);
+
+	if (err_line)
+		*err_line = n_lines;
+
+	if (err_msg)
+		switch (status) {
+		case -ENOMEM:
+			*err_msg = "Memory allocation failed.";
+			break;
+
+		default:
+			*err_msg = "Invalid timeout value statement.";
+			break;
+		}
+
+	return status;
+}
+
+
 static int
 learner_statement_parse(struct learner_spec *s,
 		      uint32_t *block_mask,
@@ -1780,6 +1877,15 @@ learner_block_parse(struct learner_spec *s,
 						   err_line,
 						   err_msg);
 
+	if (*block_mask & (1 << LEARNER_TIMEOUT_BLOCK))
+		return learner_timeout_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);
@@ -1833,28 +1939,13 @@ learner_block_parse(struct learner_spec *s,
 		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;
-	}
+	if (!strcmp(tokens[0], "timeout"))
+		return learner_timeout_statement_parse(block_mask,
+						       tokens,
+						       n_tokens,
+						       n_lines,
+						       err_line,
+						       err_msg);
 
 	/* Anything else. */
 	if (err_line)
@@ -2365,7 +2456,8 @@ rte_swx_pipeline_build_from_spec(struct rte_swx_pipeline *p,
 				learner_spec.name,
 				&learner_spec.params,
 				learner_spec.size,
-				learner_spec.timeout);
+				learner_spec.timeout,
+				learner_spec.n_timeouts);
 			if (status) {
 				if (err_line)
 					*err_line = n_lines;
diff --git a/lib/pipeline/version.map b/lib/pipeline/version.map
index 44332aad26..130278ff0d 100644
--- a/lib/pipeline/version.map
+++ b/lib/pipeline/version.map
@@ -140,4 +140,8 @@ EXPERIMENTAL {
 	rte_swx_ctl_learner_info_get;
 	rte_swx_ctl_learner_match_field_info_get;
 	rte_swx_pipeline_learner_config;
+
+	#added in 22.07
+	rte_swx_ctl_pipeline_learner_timeout_get;
+	rte_swx_ctl_pipeline_learner_timeout_set;
 };
-- 
2.17.1


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH V3 3/3] examples/pipeline: improve learner table timers
  2022-05-20 22:12   ` [PATCH V3 1/3] table: " Cristian Dumitrescu
  2022-05-20 22:12     ` [PATCH V3 2/3] pipeline: " Cristian Dumitrescu
@ 2022-05-20 22:12     ` Cristian Dumitrescu
  2022-06-01 13:57     ` [PATCH V3 1/3] table: " Thomas Monjalon
  2 siblings, 0 replies; 10+ messages in thread
From: Cristian Dumitrescu @ 2022-05-20 22:12 UTC (permalink / raw)
  To: dev

Added the rearm counter to the statistics. Updated the learner table
example to the new learner table timer operation.

Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>
---
 examples/pipeline/cli.c                 |  2 ++
 examples/pipeline/examples/learner.spec | 15 +++++++++++++--
 2 files changed, 15 insertions(+), 2 deletions(-)

diff --git a/examples/pipeline/cli.c b/examples/pipeline/cli.c
index d52ad6b61e..0334616bd9 100644
--- a/examples/pipeline/cli.c
+++ b/examples/pipeline/cli.c
@@ -2677,12 +2677,14 @@ cmd_pipeline_stats(char **tokens,
 			"\t\tMiss (packets): %" PRIu64 "\n"
 			"\t\tLearn OK (packets): %" PRIu64 "\n"
 			"\t\tLearn error (packets): %" PRIu64 "\n"
+			"\t\tRearm (packets): %" PRIu64 "\n"
 			"\t\tForget (packets): %" PRIu64 "\n",
 			learner_info.name,
 			stats.n_pkts_hit,
 			stats.n_pkts_miss,
 			stats.n_pkts_learn_ok,
 			stats.n_pkts_learn_err,
+			stats.n_pkts_rearm,
 			stats.n_pkts_forget);
 		out_size -= strlen(out);
 		out += strlen(out);
diff --git a/examples/pipeline/examples/learner.spec b/examples/pipeline/examples/learner.spec
index 4ee52da7ac..095325c293 100644
--- a/examples/pipeline/examples/learner.spec
+++ b/examples/pipeline/examples/learner.spec
@@ -48,6 +48,9 @@ struct metadata_t {
 	bit<32> port_in
 	bit<32> port_out
 
+	// Key timeout.
+	bit<32> timeout_id
+
 	// Arguments for the "fwd_action" action.
 	bit<32> fwd_action_arg_port_out
 }
@@ -68,10 +71,14 @@ struct fwd_action_args_t {
 
 action fwd_action args instanceof fwd_action_args_t {
 	mov m.port_out t.port_out
+	rearm
 	return
 }
 
 action learn_action args none {
+	// Pick the key timeout. Timeout ID #1 (i.e. 120 seconds) is selected.
+	mov m.timeout_id 1
+
 	// Read current counter value into m.fwd_action_arg_port_out.
 	regrd m.fwd_action_arg_port_out counter 0
 
@@ -84,7 +91,7 @@ action learn_action args none {
 	// Add the current lookup key to the table with fwd_action as the key action. The action
 	// arguments are read from the packet meta-data (the m.fwd_action_arg_port_out field). These
 	// packet meta-data fields have to be written before the "learn" instruction is invoked.
-	learn fwd_action m.fwd_action_arg_port_out
+	learn fwd_action m.fwd_action_arg_port_out m.timeout_id
 
 	// Send the current packet to the same output port.
 	mov m.port_out m.fwd_action_arg_port_out
@@ -110,7 +117,11 @@ learner fwd_table {
 
 	size 1048576
 
-	timeout 120
+	timeout {
+		60
+		120
+		180
+	}
 }
 
 //
-- 
2.17.1


^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH V3 1/3] table: improve learner table timers
  2022-05-20 22:12   ` [PATCH V3 1/3] table: " Cristian Dumitrescu
  2022-05-20 22:12     ` [PATCH V3 2/3] pipeline: " Cristian Dumitrescu
  2022-05-20 22:12     ` [PATCH V3 3/3] examples/pipeline: " Cristian Dumitrescu
@ 2022-06-01 13:57     ` Thomas Monjalon
  2 siblings, 0 replies; 10+ messages in thread
From: Thomas Monjalon @ 2022-06-01 13:57 UTC (permalink / raw)
  To: Cristian Dumitrescu; +Cc: dev

21/05/2022 00:12, Cristian Dumitrescu:
> Previously, on lookup hit, the hit key had its timer automatically
> rearmed with the same timeout in order to prevent its expiration. Now,
> a broader set of actions is available on lookup hit, which has to be
> managed explicitly: the key can have its timer rearmed with the same
> or with a different timeout, or the key timer can be left unmodified.
> The latter option allows the key to expire naturally when the timer
> eventually runs out, unless the key is hit again and its timer rearmed
> at that point. Needed by the TCP connection tracking state machine.
> 
> Signed-off-by: Cristian Dumitrescu <cristian.dumitrescu@intel.com>

Series applied, thanks.




^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2022-06-01 13:57 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-04-21 15:59 [PATCH 1/3] table: improve learner table timers Cristian Dumitrescu
2022-04-21 15:59 ` [PATCH 2/3] pipeline: " Cristian Dumitrescu
2022-04-21 15:59 ` [PATCH 3/3] examples/pipeline: " Cristian Dumitrescu
2022-04-22 13:03 ` [PATCH V2 1/3] table: " Cristian Dumitrescu
2022-04-22 13:03   ` [PATCH V2 2/3] pipeline: " Cristian Dumitrescu
2022-04-22 13:03   ` [PATCH V2 3/3] examples/pipeline: " Cristian Dumitrescu
2022-05-20 22:12   ` [PATCH V3 1/3] table: " Cristian Dumitrescu
2022-05-20 22:12     ` [PATCH V3 2/3] pipeline: " Cristian Dumitrescu
2022-05-20 22:12     ` [PATCH V3 3/3] examples/pipeline: " Cristian Dumitrescu
2022-06-01 13:57     ` [PATCH V3 1/3] table: " Thomas Monjalon

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).