mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Omar Ramadan <omar@blockcast.net>
To: Taehee Yoo <ap420073@gmail.com>,
	"David S. Miller" <davem@davemloft.net>,
	Eric Dumazet <edumazet@google.com>,
	Jakub Kicinski <kuba@kernel.org>, Paolo Abeni <pabeni@redhat.com>
Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org,
	michael.chan@broadcom.com, pavan.chebbi@broadcom.com,
	saeedm@nvidia.com, leon@kernel.org, tariqt@nvidia.com,
	mbloch@nvidia.com, brett.creeley@amd.com, sgoutham@marvell.com,
	anthony.l.nguyen@intel.com, przemyslaw.kitszel@intel.com,
	intel-wired-lan@lists.osuosl.org,
	Ido Schimmel <idosch@nvidia.com>,
	Satish Kharat <satishkh@cisco.com>,
	Andrew Lunn <andrew+netdev@lunn.ch>,
	Simon Horman <horms@kernel.org>, Shuah Khan <shuah@kernel.org>,
	Kees Cook <kees@kernel.org>,
	"Gustavo A. R. Silva" <gustavoars@kernel.org>,
	linux-kselftest@vger.kernel.org, linux-hardening@vger.kernel.org
Subject: [RFC PATCH net-next 7/9] netdevsim: add a TX reference provider for AMT relay offload
Date: Sat,  7 Nov 2026 18:44:35 +0000	[thread overview]
Message-ID: <20261107184437.2783174-8-omar@blockcast.net> (raw)
In-Reply-To: <20261107184437.2783174-1-omar@blockcast.net>

Each PF port offers itself as an AMT relay replication offload provider
(include/net/amt_offload.h) for relays whose stream device it is. It is
not a pass-through to amt's software fan-out: the port keeps its own
endpoint and group tables, with a staged and a committed member set per
group under the generation amt gave it, and decides from those which
endpoints get a copy of each packet amt hands to ->xmit. Each copy is
encapsulated by amt_offload_encap_xmit() and sent out of the port to its
peer, so any divergence between amt's desired state and what the
provider committed shows up as a wrong copy count at the peer. The set
committed before the last one is kept for packets already in flight,
and an endpoint stays allocated while a set names it.

debugfs (ports/<n>/amt/) shows the tables and counters and injects the
failures the contract has to survive: busy (xmit returns BUSY),
fail_commit (group_commit returns -ENOSPC), max_endpoints (endpoint_add
returns -ENOSPC), stall_ms (every programming op sleeps, so amt sees it
overrun op_timeout_ms and resets) and op_timeout_ms (advertised at
bind).

The provider owns its devlink objects, registered on netdevsim's own
devlink instance, not amt's: resources amt/endpoints, amt/groups,
amt/members and amt/cmdq, and an "amt" health reporter reported on
every flush (amt's reset). amt registers nothing on devlink.

Moving a netdev to another netns unregisters it there first, which
makes amt forget its provider; the port offers itself again once it is
registered in the new netns.

TX only: netdevsim has no ingress classifier to simulate the ingress
path with.

Assisted-by: LLM
Signed-off-by: Omar Ramadan <omar@blockcast.net>
---
 drivers/net/Kconfig               |   1 +
 drivers/net/netdevsim/Makefile    |   4 +
 drivers/net/netdevsim/amt.c       | 600 ++++++++++++++++++++++++++++++
 drivers/net/netdevsim/dev.c       |  68 ++++
 drivers/net/netdevsim/health.c    |  28 +-
 drivers/net/netdevsim/netdev.c    |  19 +-
 drivers/net/netdevsim/netdevsim.h |  27 ++
 7 files changed, 744 insertions(+), 3 deletions(-)
 create mode 100644 drivers/net/netdevsim/amt.c

diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index fda19ae4b..6e45d2fa0 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -625,6 +625,7 @@ config NETDEVSIM
 	depends on INET
 	depends on PSAMPLE || PSAMPLE=n
 	depends on PTP_1588_CLOCK_MOCK || PTP_1588_CLOCK_MOCK=n
+	depends on AMT || AMT=n
 	select NET_DEVLINK
 	select PAGE_POOL
 	select NET_SHAPER
diff --git a/drivers/net/netdevsim/Makefile b/drivers/net/netdevsim/Makefile
index 87718204f..eec03812a 100644
--- a/drivers/net/netdevsim/Makefile
+++ b/drivers/net/netdevsim/Makefile
@@ -25,3 +25,7 @@ endif
 ifneq ($(CONFIG_MACSEC),)
 netdevsim-objs += macsec.o
 endif
+
+ifneq ($(CONFIG_AMT),)
+netdevsim-objs += amt.o
+endif
diff --git a/drivers/net/netdevsim/amt.c b/drivers/net/netdevsim/amt.c
new file mode 100644
index 000000000..ffb9aa4e7
--- /dev/null
+++ b/drivers/net/netdevsim/amt.c
@@ -0,0 +1,600 @@
+// SPDX-License-Identifier: GPL-2.0
+/* netdevsim: TX reference provider for AMT relay replication offload.
+ *
+ * Each PF port registers itself as a provider for relays that transmit
+ * on it (include/net/amt_offload.h). The port keeps its OWN endpoint and
+ * group tables, with a staged and a committed member set per group under
+ * the generation amt gave it, and decides replication from them: a packet
+ * amt hands to ->xmit is copied to the endpoints of the committed set of
+ * that generation, each copy encapsulated by amt_offload_encap_xmit() and
+ * sent out of this port to its peer. Any divergence between what amt
+ * wants and what the provider committed shows up at the peer as a wrong
+ * copy count. TX only: netdevsim has no ingress classifier to simulate.
+ *
+ * debugfs, per port, under ports/<n>/amt/:
+ *   tables         the provider's endpoints and groups, read-only
+ *   tx_copies, tx_busy, tx_stale_gen, tx_dropped, commits, flushes
+ *                  counters, read-only
+ *   busy           xmit returns BUSY
+ *   fail_commit    group_commit fails with -ENOSPC
+ *   max_endpoints  endpoint_add fails with -ENOSPC beyond this (0: none)
+ *   stall_ms       every programming op sleeps this long first
+ *   op_timeout_ms  advertised to amt at bind (0: no limit)
+ *
+ * devlink, per device: resources amt/{endpoints,groups,members,cmdq} and
+ * the "amt" health reporter, reported on every flush (amt's reset).
+ */
+
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/netdevice.h>
+#include <linux/rculist.h>
+#include <linux/refcount.h>
+#include <linux/seq_file.h>
+#include <linux/skbuff.h>
+#include <net/amt_offload.h>
+#include <net/devlink.h>
+
+#include "netdevsim.h"
+
+struct amt_offload_endpoint {
+	struct list_head		node;	/* nsim_amt_bind.eps */
+	struct amt_offload_endpoint_key	key;
+	/* One for amt's handle (all endpoint_add calls for the key), one per
+	 * member set naming it: an in-flight packet may still be copied to
+	 * an endpoint amt already deleted.
+	 */
+	refcount_t			refs;
+	u32				adds;
+	atomic64_t			copies;
+	struct rcu_head			rcu;
+};
+
+struct nsim_amt_set {
+	u64				gen;
+	u32				n;
+	struct rcu_head			rcu;
+	struct amt_offload_endpoint	*eps[] __counted_by(n);
+};
+
+struct amt_offload_group {
+	struct list_head		node;	/* nsim_amt_bind.grps */
+	struct amt_offload_group_key	key;
+	struct nsim_amt_set		*staged;
+	/* Committed last, and the one before it for packets in flight. */
+	struct nsim_amt_set __rcu	*cur;
+	struct nsim_amt_set __rcu	*prev;
+	struct rcu_head			rcu;
+};
+
+struct nsim_amt_bind {
+	struct list_head		node;	/* nsim_amt.binds */
+	struct amt_offload_binding	*b;
+	struct list_head		eps;
+	struct list_head		grps;
+	u32				nr_eps;
+};
+
+struct nsim_amt {
+	struct netdevsim		*ns;
+	struct mutex			lock;	/* binds and their tables */
+	struct list_head		binds;
+	struct dentry			*ddir;
+	bool				busy;
+	bool				fail_commit;
+	u32				max_endpoints;
+	u32				stall_ms;
+	u32				op_timeout_ms;
+	u64				commits;
+	u64				flushes;
+	atomic64_t			tx_copies;
+	atomic64_t			tx_busy;
+	atomic64_t			tx_stale_gen;
+	atomic64_t			tx_dropped;
+};
+
+#define nsim_amt_deref(nam, p) \
+	rcu_dereference_protected(p, lockdep_is_held(&(nam)->lock))
+
+static struct nsim_amt *nsim_amt_of(struct amt_offload_binding *b)
+{
+	struct netdevsim *ns = netdev_priv(b->lower);
+
+	return ns->amt;
+}
+
+/* Every programming op runs inside one of these: cmdq occupancy, and the
+ * injected stall that makes amt see an op overrun op_timeout_ms.
+ */
+static struct nsim_amt *nsim_amt_op_begin(struct amt_offload_binding *b)
+{
+	struct nsim_amt *nam = nsim_amt_of(b);
+	u32 stall = READ_ONCE(nam->stall_ms);
+
+	atomic_inc(&nam->ns->nsim_dev->amt.cmdq);
+	if (stall)
+		msleep(stall);
+	mutex_lock(&nam->lock);
+	return nam;
+}
+
+static void nsim_amt_op_end(struct nsim_amt *nam)
+{
+	mutex_unlock(&nam->lock);
+	atomic_dec(&nam->ns->nsim_dev->amt.cmdq);
+}
+
+static void nsim_amt_ep_put(struct amt_offload_endpoint *ep)
+{
+	if (refcount_dec_and_test(&ep->refs))
+		kfree_rcu(ep, rcu);
+}
+
+static void nsim_amt_set_free_rcu(struct rcu_head *head)
+{
+	struct nsim_amt_set *set = container_of(head, struct nsim_amt_set,
+						rcu);
+	u32 i;
+
+	for (i = 0; i < set->n; i++)
+		nsim_amt_ep_put(set->eps[i]);
+	kfree(set);
+}
+
+static void nsim_amt_set_free(struct nsim_amt_set *set)
+{
+	if (set)
+		call_rcu(&set->rcu, nsim_amt_set_free_rcu);
+}
+
+static int nsim_amt_bind(struct amt_offload_binding *b,
+			 struct amt_offload_caps *caps)
+{
+	struct nsim_amt *nam = nsim_amt_of(b);
+	struct nsim_amt_bind *nb;
+
+	nb = kzalloc_obj(*nb, GFP_KERNEL);
+	if (!nb)
+		return -ENOMEM;
+	nb->b = b;
+	INIT_LIST_HEAD(&nb->eps);
+	INIT_LIST_HEAD(&nb->grps);
+	b->priv = nb;
+
+	caps->families = AMT_OFFLOAD_FAMILY_V4 | AMT_OFFLOAD_FAMILY_V6;
+	caps->paths = BIT(AMT_OFFLOAD_PATH_TX);
+	caps->op_timeout_ms = READ_ONCE(nam->op_timeout_ms);
+	caps->max_endpoints = READ_ONCE(nam->max_endpoints);
+
+	mutex_lock(&nam->lock);
+	list_add_tail(&nb->node, &nam->binds);
+	mutex_unlock(&nam->lock);
+	return 0;
+}
+
+/* Drop every group and endpoint of @nb. Caller holds nam->lock; amt has
+ * stopped submitting packets for this binding.
+ */
+static void nsim_amt_bind_clear(struct nsim_amt *nam, struct nsim_amt_bind *nb)
+{
+	struct nsim_dev *nsim_dev = nam->ns->nsim_dev;
+	struct amt_offload_endpoint *ep, *et;
+	struct amt_offload_group *g, *gt;
+	struct nsim_amt_set *set;
+
+	list_for_each_entry_safe(g, gt, &nb->grps, node) {
+		set = nsim_amt_deref(nam, g->cur);
+		if (set)
+			atomic_sub(set->n, &nsim_dev->amt.members);
+		nsim_amt_set_free(set);
+		nsim_amt_set_free(nsim_amt_deref(nam, g->prev));
+		nsim_amt_set_free(g->staged);
+		list_del(&g->node);
+		atomic_dec(&nsim_dev->amt.groups);
+		kfree_rcu(g, rcu);
+	}
+	list_for_each_entry_safe(ep, et, &nb->eps, node) {
+		list_del(&ep->node);
+		nb->nr_eps--;
+		atomic_dec(&nsim_dev->amt.endpoints);
+		nsim_amt_ep_put(ep);
+	}
+}
+
+static void nsim_amt_unbind(struct amt_offload_binding *b)
+{
+	struct nsim_amt *nam = nsim_amt_of(b);
+	struct nsim_amt_bind *nb = b->priv;
+
+	mutex_lock(&nam->lock);
+	/* Empty unless amt broke the teardown order; then free, not leak. */
+	WARN_ON_ONCE(!list_empty(&nb->grps) || !list_empty(&nb->eps));
+	nsim_amt_bind_clear(nam, nb);
+	list_del(&nb->node);
+	mutex_unlock(&nam->lock);
+	kfree(nb);
+}
+
+static bool nsim_amt_ep_key_eq(const struct amt_offload_endpoint_key *a,
+			       const struct amt_offload_endpoint_key *b)
+{
+	if (a->family != b->family || a->port != b->port)
+		return false;
+	if (a->family == AF_INET6)
+		return ipv6_addr_equal(&a->addr.ip6, &b->addr.ip6);
+	return a->addr.ip4 == b->addr.ip4;
+}
+
+static int nsim_amt_endpoint_add(struct amt_offload_binding *b,
+				 const struct amt_offload_endpoint_key *key,
+				 struct amt_offload_endpoint **epp)
+{
+	struct nsim_amt *nam = nsim_amt_op_begin(b);
+	struct nsim_amt_bind *nb = b->priv;
+	struct amt_offload_endpoint *ep;
+	int err = 0;
+
+	list_for_each_entry(ep, &nb->eps, node) {
+		if (nsim_amt_ep_key_eq(&ep->key, key)) {
+			ep->adds++;
+			goto out;
+		}
+	}
+	if (nam->max_endpoints && nb->nr_eps >= nam->max_endpoints) {
+		err = -ENOSPC;
+		goto unlock;
+	}
+	ep = kzalloc_obj(*ep, GFP_KERNEL);
+	if (!ep) {
+		err = -ENOMEM;
+		goto unlock;
+	}
+	ep->key = *key;
+	refcount_set(&ep->refs, 1);
+	ep->adds = 1;
+	list_add_tail(&ep->node, &nb->eps);
+	nb->nr_eps++;
+	atomic_inc(&nam->ns->nsim_dev->amt.endpoints);
+out:
+	*epp = ep;
+unlock:
+	nsim_amt_op_end(nam);
+	return err;
+}
+
+static void nsim_amt_endpoint_del(struct amt_offload_binding *b,
+				  struct amt_offload_endpoint *ep)
+{
+	struct nsim_amt *nam = nsim_amt_op_begin(b);
+	struct nsim_amt_bind *nb = b->priv;
+
+	if (!--ep->adds) {
+		list_del(&ep->node);
+		nb->nr_eps--;
+		atomic_dec(&nam->ns->nsim_dev->amt.endpoints);
+		nsim_amt_ep_put(ep);
+	}
+	nsim_amt_op_end(nam);
+}
+
+static int nsim_amt_group_update(struct amt_offload_binding *b,
+				 const struct amt_offload_group_key *key,
+				 struct amt_offload_endpoint *const *members,
+				 unsigned int nr_members, u64 gen,
+				 struct amt_offload_group **grpp)
+{
+	struct nsim_amt *nam = nsim_amt_op_begin(b);
+	struct amt_offload_group *g = *grpp;
+	struct nsim_amt_bind *nb = b->priv;
+	struct nsim_amt_set *set;
+	int err = 0;
+	u32 i;
+
+	if (!g) {
+		g = kzalloc_obj(*g, GFP_KERNEL);
+		if (!g) {
+			err = -ENOMEM;
+			goto out;
+		}
+		g->key = *key;
+		list_add_tail(&g->node, &nb->grps);
+		atomic_inc(&nam->ns->nsim_dev->amt.groups);
+		*grpp = g;
+	}
+	set = kzalloc(struct_size(set, eps, nr_members), GFP_KERNEL);
+	if (!set) {
+		err = -ENOMEM;
+		goto out;
+	}
+	set->n = nr_members;
+	set->gen = gen;
+	for (i = 0; i < nr_members; i++) {
+		refcount_inc(&members[i]->refs);
+		set->eps[i] = members[i];
+	}
+	nsim_amt_set_free(g->staged);
+	g->staged = set;
+out:
+	nsim_amt_op_end(nam);
+	return err;
+}
+
+static int nsim_amt_group_commit(struct amt_offload_binding *b,
+				 struct amt_offload_group *g, u64 gen)
+{
+	struct nsim_amt *nam = nsim_amt_op_begin(b);
+	struct nsim_dev *nsim_dev = nam->ns->nsim_dev;
+	struct nsim_amt_set *cur, *prev;
+	int err = 0;
+
+	if (nam->fail_commit) {
+		err = -ENOSPC;
+		goto out;
+	}
+	if (!g->staged || g->staged->gen != gen) {
+		err = -EINVAL;
+		goto out;
+	}
+	cur = nsim_amt_deref(nam, g->cur);
+	prev = nsim_amt_deref(nam, g->prev);
+	atomic_add(g->staged->n, &nsim_dev->amt.members);
+	if (cur)
+		atomic_sub(cur->n, &nsim_dev->amt.members);
+	rcu_assign_pointer(g->prev, cur);
+	rcu_assign_pointer(g->cur, g->staged);
+	g->staged = NULL;
+	nsim_amt_set_free(prev);
+	nam->commits++;
+out:
+	nsim_amt_op_end(nam);
+	return err;
+}
+
+static void nsim_amt_group_disable(struct amt_offload_binding *b,
+				   struct amt_offload_group *g)
+{
+	struct nsim_amt *nam = nsim_amt_op_begin(b);
+	struct nsim_amt_set *set;
+
+	set = nsim_amt_deref(nam, g->cur);
+	if (set)
+		atomic_sub(set->n, &nam->ns->nsim_dev->amt.members);
+	nsim_amt_set_free(set);
+	nsim_amt_set_free(nsim_amt_deref(nam, g->prev));
+	nsim_amt_set_free(g->staged);
+	list_del(&g->node);
+	atomic_dec(&nam->ns->nsim_dev->amt.groups);
+	kfree_rcu(g, rcu);
+	nsim_amt_op_end(nam);
+}
+
+/* Softirq, under rcu_read_lock(). Copies go to the committed set of @gen
+ * only; a generation this provider never committed (or has already
+ * retired) is declined, never guessed at.
+ */
+static enum amt_offload_result
+nsim_amt_xmit(struct amt_offload_binding *b, struct amt_offload_group *g,
+	      struct sk_buff *skb, u64 gen)
+{
+	struct nsim_amt *nam = nsim_amt_of(b);
+	struct nsim_amt_set *set;
+	u32 i;
+
+	if (READ_ONCE(nam->busy)) {
+		atomic64_inc(&nam->tx_busy);
+		return AMT_OFFLOAD_BUSY;
+	}
+	set = rcu_dereference(g->cur);
+	if (!set || set->gen != gen) {
+		set = rcu_dereference(g->prev);
+		if (!set || set->gen != gen) {
+			atomic64_inc(&nam->tx_stale_gen);
+			return AMT_OFFLOAD_FALLBACK;
+		}
+	}
+	for (i = 0; i < set->n; i++) {
+		if (amt_offload_encap_xmit(b, &set->eps[i]->key, skb)) {
+			atomic64_inc(&nam->tx_dropped);
+			continue;
+		}
+		atomic64_inc(&set->eps[i]->copies);
+		atomic64_inc(&nam->tx_copies);
+	}
+	consume_skb(skb);
+	return AMT_OFFLOAD_CONSUMED;
+}
+
+/* Called under amt's offload mutex, like every programming op: from
+ * the reconciler with no other lock, or at teardown with RTNL held and,
+ * when the port is being deleted, the devlink instance lock too. The
+ * "amt" reporter has no .recover and no .dump (health.c), so reporting
+ * it takes no devlink lock in either case.
+ */
+static void nsim_amt_flush(struct amt_offload_binding *b)
+{
+	struct nsim_amt *nam = nsim_amt_op_begin(b);
+
+	nsim_amt_bind_clear(nam, b->priv);
+	nam->flushes++;
+	nsim_amt_op_end(nam);
+	devlink_health_report(nam->ns->nsim_dev->health.amt_reporter,
+			      "amt reset: provider tables flushed", NULL);
+}
+
+static const struct amt_offload_ops nsim_amt_ops = {
+	.bind		= nsim_amt_bind,
+	.unbind		= nsim_amt_unbind,
+	.endpoint_add	= nsim_amt_endpoint_add,
+	.endpoint_del	= nsim_amt_endpoint_del,
+	.group_update	= nsim_amt_group_update,
+	.group_commit	= nsim_amt_group_commit,
+	.group_disable	= nsim_amt_group_disable,
+	.xmit		= nsim_amt_xmit,
+	.flush		= nsim_amt_flush,
+};
+
+static void nsim_amt_show_ep(struct seq_file *m,
+			     const struct amt_offload_endpoint_key *key)
+{
+	if (key->family == AF_INET6)
+		seq_printf(m, "[%pI6c]:%u", &key->addr.ip6, ntohs(key->port));
+	else
+		seq_printf(m, "%pI4:%u", &key->addr.ip4, ntohs(key->port));
+}
+
+static void nsim_amt_show_set(struct seq_file *m, const char *name,
+			      const struct nsim_amt_set *set)
+{
+	u32 i;
+
+	if (!set)
+		return;
+	seq_printf(m, "  %s gen %llu members %u:", name, set->gen, set->n);
+	for (i = 0; i < set->n; i++) {
+		seq_putc(m, ' ');
+		nsim_amt_show_ep(m, &set->eps[i]->key);
+	}
+	seq_putc(m, '\n');
+}
+
+static int nsim_amt_tables_show(struct seq_file *m, void *unused)
+{
+	struct nsim_amt *nam = m->private;
+	struct amt_offload_endpoint *ep;
+	struct amt_offload_group *g;
+	struct nsim_amt_bind *nb;
+
+	mutex_lock(&nam->lock);
+	list_for_each_entry(nb, &nam->binds, node) {
+		seq_printf(m, "binding %s epoch %llu\n", netdev_name(nb->b->dev),
+			   nb->b->epoch);
+		list_for_each_entry(ep, &nb->eps, node) {
+			seq_puts(m, "endpoint ");
+			nsim_amt_show_ep(m, &ep->key);
+			seq_printf(m, " copies %llu\n",
+				   atomic64_read(&ep->copies));
+		}
+		list_for_each_entry(g, &nb->grps, node) {
+			if (g->key.family == AF_INET6)
+				seq_printf(m, "group %pI6c\n", &g->key.group.ip6);
+			else
+				seq_printf(m, "group %pI4\n", &g->key.group.ip4);
+			nsim_amt_show_set(m, "committed",
+					  nsim_amt_deref(nam, g->cur));
+			nsim_amt_show_set(m, "previous",
+					  nsim_amt_deref(nam, g->prev));
+			nsim_amt_show_set(m, "staged", g->staged);
+		}
+	}
+	mutex_unlock(&nam->lock);
+	return 0;
+}
+DEFINE_SHOW_ATTRIBUTE(nsim_amt_tables);
+
+static int nsim_amt_atomic64_get(void *data, u64 *val)
+{
+	*val = atomic64_read(data);
+	return 0;
+}
+DEFINE_DEBUGFS_ATTRIBUTE(nsim_amt_atomic64_fops, nsim_amt_atomic64_get,
+			 NULL, "%llu\n");
+
+static void nsim_amt_debugfs_init(struct nsim_amt *nam)
+{
+	struct dentry *d;
+
+	d = debugfs_create_dir("amt", nam->ns->nsim_dev_port->ddir);
+	nam->ddir = d;
+	debugfs_create_file("tables", 0400, d, nam, &nsim_amt_tables_fops);
+	debugfs_create_file("tx_copies", 0400, d, &nam->tx_copies,
+			    &nsim_amt_atomic64_fops);
+	debugfs_create_file("tx_busy", 0400, d, &nam->tx_busy,
+			    &nsim_amt_atomic64_fops);
+	debugfs_create_file("tx_stale_gen", 0400, d, &nam->tx_stale_gen,
+			    &nsim_amt_atomic64_fops);
+	debugfs_create_file("tx_dropped", 0400, d, &nam->tx_dropped,
+			    &nsim_amt_atomic64_fops);
+	debugfs_create_u64("commits", 0400, d, &nam->commits);
+	debugfs_create_u64("flushes", 0400, d, &nam->flushes);
+	debugfs_create_bool("busy", 0600, d, &nam->busy);
+	debugfs_create_bool("fail_commit", 0600, d, &nam->fail_commit);
+	debugfs_create_u32("max_endpoints", 0600, d, &nam->max_endpoints);
+	debugfs_create_u32("stall_ms", 0600, d, &nam->stall_ms);
+	debugfs_create_u32("op_timeout_ms", 0600, d, &nam->op_timeout_ms);
+}
+
+int nsim_amt_init(struct netdevsim *ns)
+{
+	struct nsim_amt *nam;
+	int err;
+
+	nam = kzalloc_obj(*nam, GFP_KERNEL);
+	if (!nam)
+		return -ENOMEM;
+	nam->ns = ns;
+	mutex_init(&nam->lock);
+	INIT_LIST_HEAD(&nam->binds);
+	nam->op_timeout_ms = 1000;
+	nsim_amt_debugfs_init(nam);
+
+	rtnl_lock();
+	ns->amt = nam;
+	err = amt_offload_register(ns->netdev, &nsim_amt_ops);
+	if (err)
+		ns->amt = NULL;
+	rtnl_unlock();
+	if (err) {
+		debugfs_remove_recursive(nam->ddir);
+		mutex_destroy(&nam->lock);
+		kfree(nam);
+	}
+	return err;
+}
+
+void nsim_amt_uninit(struct netdevsim *ns)
+{
+	struct nsim_amt *nam = ns->amt;
+
+	if (!nam)
+		return;
+	rtnl_lock();
+	amt_offload_unregister(ns->netdev);
+	ns->amt = NULL;
+	rtnl_unlock();
+	debugfs_remove_recursive(nam->ddir);
+	mutex_destroy(&nam->lock);
+	kfree(nam);
+}
+
+/* amt forgets a provider when its lower device is unregistered, and
+ * moving a netdev to another netns unregisters it there first. Offer
+ * the port again once it is registered in its new netns.
+ */
+static int nsim_amt_netdev_event(struct notifier_block *nb,
+				 unsigned long event, void *ptr)
+{
+	struct net_device *dev = netdev_notifier_info_to_dev(ptr);
+	struct netdevsim *ns;
+
+	if (event != NETDEV_REGISTER || !netdev_is_nsim(dev))
+		return NOTIFY_DONE;
+	ns = netdev_priv(dev);
+	if (ns->amt)
+		amt_offload_register(dev, &nsim_amt_ops);
+	return NOTIFY_DONE;
+}
+
+static struct notifier_block nsim_amt_notifier = {
+	.notifier_call = nsim_amt_netdev_event,
+};
+
+int nsim_amt_module_init(void)
+{
+	return register_netdevice_notifier(&nsim_amt_notifier);
+}
+
+void nsim_amt_module_exit(void)
+{
+	unregister_netdevice_notifier(&nsim_amt_notifier);
+	rcu_barrier();	/* nsim_amt_set_free_rcu() is module text */
+}
diff --git a/drivers/net/netdevsim/dev.c b/drivers/net/netdevsim/dev.c
index f00fc2f9e..7fcdb0d67 100644
--- a/drivers/net/netdevsim/dev.c
+++ b/drivers/net/netdevsim/dev.c
@@ -444,6 +444,69 @@ static void nsim_dev_port_debugfs_exit(struct nsim_dev_port *nsim_dev_port)
 	debugfs_remove_recursive(nsim_dev_port->ddir);
 }
 
+static u64 nsim_dev_amt_endpoints_occ_get(void *priv)
+{
+	return atomic_read(&((struct nsim_dev *)priv)->amt.endpoints);
+}
+
+static u64 nsim_dev_amt_groups_occ_get(void *priv)
+{
+	return atomic_read(&((struct nsim_dev *)priv)->amt.groups);
+}
+
+static u64 nsim_dev_amt_members_occ_get(void *priv)
+{
+	return atomic_read(&((struct nsim_dev *)priv)->amt.members);
+}
+
+static u64 nsim_dev_amt_cmdq_occ_get(void *priv)
+{
+	return atomic_read(&((struct nsim_dev *)priv)->amt.cmdq);
+}
+
+static int
+nsim_dev_amt_resources_register(struct devlink *devlink,
+				const struct devlink_resource_size_params *p)
+{
+	static const struct {
+		const char *name;
+		u64 id;
+		devlink_resource_occ_get_t *occ_get;
+	} res[] = {
+		{ "endpoints", NSIM_RESOURCE_AMT_ENDPOINTS,
+		  nsim_dev_amt_endpoints_occ_get },
+		{ "groups", NSIM_RESOURCE_AMT_GROUPS,
+		  nsim_dev_amt_groups_occ_get },
+		{ "members", NSIM_RESOURCE_AMT_MEMBERS,
+		  nsim_dev_amt_members_occ_get },
+		{ "cmdq", NSIM_RESOURCE_AMT_CMDQ, nsim_dev_amt_cmdq_occ_get },
+	};
+	int err, i;
+
+	if (!IS_ENABLED(CONFIG_AMT))
+		return 0;
+	err = devl_resource_register(devlink, "amt", (u64)-1,
+				     NSIM_RESOURCE_AMT,
+				     DEVLINK_RESOURCE_ID_PARENT_TOP, p);
+	if (err) {
+		pr_err("Failed to register AMT top resource\n");
+		return err;
+	}
+	for (i = 0; i < ARRAY_SIZE(res); i++) {
+		err = devl_resource_register(devlink, res[i].name, (u64)-1,
+					     res[i].id, NSIM_RESOURCE_AMT, p);
+		if (err) {
+			pr_err("Failed to register AMT %s resource\n",
+			       res[i].name);
+			return err;
+		}
+		devl_resource_occ_get_register(devlink, res[i].id,
+					       res[i].occ_get,
+					       devlink_priv(devlink));
+	}
+	return 0;
+}
+
 static int nsim_dev_resources_register(struct devlink *devlink)
 {
 	struct devlink_resource_size_params params = {
@@ -514,6 +577,11 @@ static int nsim_dev_resources_register(struct devlink *devlink)
 		pr_err("Failed to register NEXTHOPS resource\n");
 		goto err_out;
 	}
+
+	/* AMT relay offload tables, owned by the ports' provider (amt.c) */
+	err = nsim_dev_amt_resources_register(devlink, &params);
+	if (err)
+		goto err_out;
 	return 0;
 
 err_out:
diff --git a/drivers/net/netdevsim/health.c b/drivers/net/netdevsim/health.c
index 3bd0e7a48..f6bc6cb51 100644
--- a/drivers/net/netdevsim/health.c
+++ b/drivers/net/netdevsim/health.c
@@ -175,6 +175,16 @@ static const struct file_operations nsim_dev_health_break_fops = {
 	.owner = THIS_MODULE,
 };
 
+/* Reported by the AMT relay offload provider (amt.c) each time amt resets
+ * it. Recovery is amt's own replay, so there is no .recover, and no .dump:
+ * with neither, devlink_health_report() never takes the devlink instance
+ * lock, which amt's ->flush() may not take (amt holds its offload mutex,
+ * below rtnl, and the instance lock sits above rtnl).
+ */
+static const struct devlink_health_reporter_ops nsim_dev_amt_reporter_ops = {
+	.name = "amt",
+};
+
 int nsim_dev_health_init(struct nsim_dev *nsim_dev, struct devlink *devlink)
 {
 	struct nsim_dev_health *health = &nsim_dev->health;
@@ -196,10 +206,21 @@ int nsim_dev_health_init(struct nsim_dev *nsim_dev, struct devlink *devlink)
 		goto err_empty_reporter_destroy;
 	}
 
+	if (IS_ENABLED(CONFIG_AMT)) {
+		health->amt_reporter =
+			devl_health_reporter_create(devlink,
+						    &nsim_dev_amt_reporter_ops,
+						    health);
+		if (IS_ERR(health->amt_reporter)) {
+			err = PTR_ERR(health->amt_reporter);
+			goto err_dummy_reporter_destroy;
+		}
+	}
+
 	health->ddir = debugfs_create_dir("health", nsim_dev->ddir);
 	if (IS_ERR(health->ddir)) {
 		err = PTR_ERR(health->ddir);
-		goto err_dummy_reporter_destroy;
+		goto err_amt_reporter_destroy;
 	}
 
 	health->recovered_break_msg = NULL;
@@ -213,6 +234,9 @@ int nsim_dev_health_init(struct nsim_dev *nsim_dev, struct devlink *devlink)
 			    &health->fail_recover);
 	return 0;
 
+err_amt_reporter_destroy:
+	if (health->amt_reporter)
+		devl_health_reporter_destroy(health->amt_reporter);
 err_dummy_reporter_destroy:
 	devl_health_reporter_destroy(health->dummy_reporter);
 err_empty_reporter_destroy:
@@ -226,6 +250,8 @@ void nsim_dev_health_exit(struct nsim_dev *nsim_dev)
 
 	debugfs_remove_recursive(health->ddir);
 	kfree(health->recovered_break_msg);
+	if (health->amt_reporter)
+		devl_health_reporter_destroy(health->amt_reporter);
 	devl_health_reporter_destroy(health->dummy_reporter);
 	devl_health_reporter_destroy(health->empty_reporter);
 }
diff --git a/drivers/net/netdevsim/netdev.c b/drivers/net/netdevsim/netdev.c
index a75076891..4294c026d 100644
--- a/drivers/net/netdevsim/netdev.c
+++ b/drivers/net/netdevsim/netdev.c
@@ -1075,6 +1075,10 @@ static int nsim_init_netdevsim(struct netdevsim *ns)
 	if (err)
 		goto err_unregister_netdev;
 
+	err = nsim_amt_init(ns);
+	if (err)
+		goto err_psp_uninit;
+
 	if (IS_ENABLED(CONFIG_DEBUG_NET)) {
 		ns->nb.notifier_call = netdev_debug_event;
 		if (register_netdevice_notifier_dev_net(ns->netdev, &ns->nb,
@@ -1084,6 +1088,8 @@ static int nsim_init_netdevsim(struct netdevsim *ns)
 
 	return 0;
 
+err_psp_uninit:
+	nsim_psp_uninit(ns);
 err_unregister_netdev:
 	rtnl_lock();
 	peer = rtnl_dereference(ns->peer);
@@ -1182,8 +1188,10 @@ void nsim_destroy(struct netdevsim *ns)
 		unregister_netdevice_notifier_dev_net(ns->netdev, &ns->nb,
 						      &ns->nn);
 
-	if (nsim_dev_port_is_pf(ns->nsim_dev_port))
+	if (nsim_dev_port_is_pf(ns->nsim_dev_port)) {
+		nsim_amt_uninit(ns);
 		nsim_psp_uninit(ns);
+	}
 
 	rtnl_lock();
 	peer = rtnl_dereference(ns->peer);
@@ -1238,10 +1246,14 @@ static int __init nsim_module_init(void)
 {
 	int err;
 
-	err = nsim_dev_init();
+	err = nsim_amt_module_init();
 	if (err)
 		return err;
 
+	err = nsim_dev_init();
+	if (err)
+		goto err_amt_exit;
+
 	err = nsim_bus_init();
 	if (err)
 		goto err_dev_exit;
@@ -1256,6 +1268,8 @@ static int __init nsim_module_init(void)
 	nsim_bus_exit();
 err_dev_exit:
 	nsim_dev_exit();
+err_amt_exit:
+	nsim_amt_module_exit();
 	return err;
 }
 
@@ -1264,6 +1278,7 @@ static void __exit nsim_module_exit(void)
 	rtnl_link_unregister(&nsim_link_ops);
 	nsim_bus_exit();
 	nsim_dev_exit();
+	nsim_amt_module_exit();
 }
 
 module_init(nsim_module_init);
diff --git a/drivers/net/netdevsim/netdevsim.h b/drivers/net/netdevsim/netdevsim.h
index d909c4160..549afee0d 100644
--- a/drivers/net/netdevsim/netdevsim.h
+++ b/drivers/net/netdevsim/netdevsim.h
@@ -127,6 +127,8 @@ struct netdevsim {
 		u32 assoc_cnt;
 	} psp;
 
+	struct nsim_amt *amt;
+
 	struct nsim_bus_dev *nsim_bus_dev;
 
 	struct bpf_prog	*bpf_offloaded;
@@ -232,6 +234,11 @@ enum nsim_resource_id {
 	NSIM_RESOURCE_IPV6_FIB,
 	NSIM_RESOURCE_IPV6_FIB_RULES,
 	NSIM_RESOURCE_NEXTHOPS,
+	NSIM_RESOURCE_AMT,
+	NSIM_RESOURCE_AMT_ENDPOINTS,
+	NSIM_RESOURCE_AMT_GROUPS,
+	NSIM_RESOURCE_AMT_MEMBERS,
+	NSIM_RESOURCE_AMT_CMDQ,
 };
 
 enum nsim_port_resource_id {
@@ -241,6 +248,7 @@ enum nsim_port_resource_id {
 struct nsim_dev_health {
 	struct devlink_health_reporter *empty_reporter;
 	struct devlink_health_reporter *dummy_reporter;
+	struct devlink_health_reporter *amt_reporter;
 	struct dentry *ddir;
 	char *recovered_break_msg;
 	u32 binary_len;
@@ -351,6 +359,13 @@ struct nsim_dev {
 	bool fail_reload;
 	struct devlink_region *dummy_region;
 	struct nsim_dev_health health;
+	/* AMT relay offload tables of every port, for devlink resources */
+	struct {
+		atomic_t endpoints;
+		atomic_t groups;
+		atomic_t members;
+		atomic_t cmdq;
+	} amt;
 	struct nsim_dev_hwstats hwstats;
 	struct flow_action_cookie *fa_cookie;
 	spinlock_t fa_cookie_lock; /* protects fa_cookie */
@@ -472,6 +487,18 @@ nsim_psp_handle_ext(struct sk_buff *skb, struct skb_ext *psp_ext) {}
 int nsim_setup_tc(struct net_device *dev, enum tc_setup_type type,
 		  void *type_data);
 
+#if IS_ENABLED(CONFIG_AMT)
+int nsim_amt_init(struct netdevsim *ns);
+void nsim_amt_uninit(struct netdevsim *ns);
+int nsim_amt_module_init(void);
+void nsim_amt_module_exit(void);
+#else
+static inline int nsim_amt_init(struct netdevsim *ns) { return 0; }
+static inline void nsim_amt_uninit(struct netdevsim *ns) {}
+static inline int nsim_amt_module_init(void) { return 0; }
+static inline void nsim_amt_module_exit(void) {}
+#endif
+
 struct nsim_bus_dev {
 	struct device dev;
 	struct list_head list;
-- 
2.43.0


  parent reply	other threads:[~2026-11-07 18:44 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-07 18:44 [RFC PATCH net-next 0/9] amt: relay replication offload provider contract (RFC, not for merge, seeking a provider) Omar Ramadan
2026-11-07 18:44 ` [RFC PATCH net-next 1/9] amt: add the relay replication offload provider header Omar Ramadan
2026-11-07 18:44 ` [RFC PATCH net-next 2/9] amt: offload provider registry and lower-device lifecycle Omar Ramadan
2026-11-07 18:44 ` [RFC PATCH net-next 3/9] amt: offload group object, desired state and reconciler Omar Ramadan
2026-11-07 18:44 ` [RFC PATCH net-next 4/9] amt: offload TX handoff, ingress punt rule and counters Omar Ramadan
2026-11-07 18:44 ` [RFC PATCH net-next 5/9] amt: add IFLA_AMT_OFFLOAD_MODE Omar Ramadan
2026-11-07 18:44 ` [RFC PATCH net-next 6/9] amt: KUnit cases for the offload contract Omar Ramadan
2026-11-07 18:44 ` Omar Ramadan [this message]
2026-11-07 18:44 ` [RFC PATCH net-next 8/9] selftests: net: add amt_offload.sh for AMT relay offload Omar Ramadan
2026-11-07 18:44 ` [RFC PATCH net-next 9/9] MAINTAINERS: amt: cover the offload header and netdevsim provider Omar Ramadan

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261107184437.2783174-8-omar@blockcast.net \
    --to=omar@blockcast.net \
    --cc=andrew+netdev@lunn.ch \
    --cc=anthony.l.nguyen@intel.com \
    --cc=ap420073@gmail.com \
    --cc=brett.creeley@amd.com \
    --cc=davem@davemloft.net \
    --cc=edumazet@google.com \
    --cc=gustavoars@kernel.org \
    --cc=horms@kernel.org \
    --cc=idosch@nvidia.com \
    --cc=intel-wired-lan@lists.osuosl.org \
    --cc=kees@kernel.org \
    --cc=kuba@kernel.org \
    --cc=leon@kernel.org \
    --cc=linux-hardening@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=mbloch@nvidia.com \
    --cc=michael.chan@broadcom.com \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=pavan.chebbi@broadcom.com \
    --cc=przemyslaw.kitszel@intel.com \
    --cc=saeedm@nvidia.com \
    --cc=satishkh@cisco.com \
    --cc=sgoutham@marvell.com \
    --cc=shuah@kernel.org \
    --cc=tariqt@nvidia.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®