From: Konstantin Sinyuk <ksinyuk@kernel.org>
To: dri-devel@lists.freedesktop.org
Cc: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>,
Francois Dugast <francois.dugast@intel.com>,
David Airlie <airlied@gmail.com>, Simona Vetter <simona@ffwll.ch>,
Maxime Ripard <mripard@kernel.org>,
Thomas Zimmermann <tzimmermann@suse.de>,
Jonathan Corbet <corbet@lwn.net>,
Shuah Khan <skhan@linuxfoundation.org>,
Donald Hunter <donald.hunter@gmail.com>,
Jakub Kicinski <kuba@kernel.org>,
"David S. Miller" <davem@davemloft.net>,
Eric Dumazet <edumazet@google.com>,
Paolo Abeni <pabeni@redhat.com>, Simon Horman <horms@kernel.org>,
Ilia Levi <ilia.levi@intel.com>,
Rodrigo Vivi <rodrigo.vivi@intel.com>,
linux-doc@vger.kernel.org, linux-kselftest@vger.kernel.org,
netdev@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: [RFC PATCH 04/12] drm/fabric: add read-only synthetic provider
Date: Mon, 24 Aug 2026 11:09:32 +0300 [thread overview]
Message-ID: <e5c0fecf67dbe72f8dd6c49489833ebdf23c5a90.1787552412.git.ksinyuk@kernel.org> (raw)
In-Reply-To: <cover.1787552412.git.ksinyuk@kernel.org>
Add drm_fabric_sim (fabricsim), a software-only provider modeled on
netdevsim. It registers deterministic linear, mesh and switch-facing
topologies through the same provider API a hardware driver uses, so the
object model and query uAPI can be exercised without accelerator hardware.
The switch shape uses opaque peers that do not resolve to registered
endpoints.
CONFIG_DRM_FABRIC_SIM builds the provider as drm-fabric-sim.ko. Module
parameters select the topology shape and initial endpoint and port counts.
With topology=linear num_endpoints=2, an endpoint is queried using the
in-tree YNL tool:
$ ./tools/net/ynl/pyynl/cli.py \
--spec Documentation/netlink/specs/drm_fabric.yaml \
--do endpoint-get --json '{"endpoint-id": 1}'
{'endpoint': {'bus-name': 'platform',
'dev-name': 'fabricsim.1',
'endpoint-id': 1,
'fabric-ep-id': 257,
'fabric-id': 1,
'name': 'sim-ep1'},
'topology-generation': 18}
The reply separates the core-assigned endpoint-id from the provider-defined
fabric-ep-id and carries the topology-generation token.
Report maximum link capabilities at registration, operational state through
drm_fabric_port_set_oper() and optional port statistics through the
port_stats_get callback.
Test-only debugfs controls stimulate provider behavior for the selftests.
They are not uAPI; observable results are reported through the drm-fabric
Generic Netlink family.
Signed-off-by: Konstantin Sinyuk <ksinyuk@kernel.org>
Assisted-by: GitHub-Copilot:claude-opus-4.8
---
Documentation/gpu/drm-fabric.rst | 46 +-
drivers/gpu/drm/fabric/Kconfig | 10 +
drivers/gpu/drm/fabric/Makefile | 3 +
drivers/gpu/drm/fabric/drm_fabric_sim.c | 978 ++++++++++++++++++++++++
4 files changed, 1034 insertions(+), 3 deletions(-)
create mode 100644 drivers/gpu/drm/fabric/drm_fabric_sim.c
diff --git a/Documentation/gpu/drm-fabric.rst b/Documentation/gpu/drm-fabric.rst
index a8b33d6cd618..8bd3633d41be 100644
--- a/Documentation/gpu/drm-fabric.rst
+++ b/Documentation/gpu/drm-fabric.rst
@@ -291,10 +291,16 @@ Query the topology with the in-tree YNL tool, pointing it at the spec:
--spec Documentation/netlink/specs/drm_fabric.yaml \
--dump fabric-get
-Replies follow the shapes described above; a provider must be registered for
-the topology to be non-empty.
+Against drm_fabric_sim loaded with ``topology=linear num_endpoints=2``
+(both non-default, for a minimal example), this returns:
-List the endpoints of a fabric:
+.. code-block:: text
+
+ [{'fabric': {'fabric-id': 1, 'instance-id': 2156317438,
+ 'name': 'fabricsim', 'type': 'synthetic'},
+ 'topology-generation': 18}]
+
+List the endpoints of that fabric:
.. code-block:: bash
@@ -302,6 +308,15 @@ List the endpoints of a fabric:
--spec Documentation/netlink/specs/drm_fabric.yaml \
--dump endpoint-get --json '{"fabric-id": 1}'
+.. code-block:: text
+
+ [{'endpoint': {'bus-name': 'platform', 'dev-name': 'fabricsim.0',
+ 'endpoint-id': 0, 'fabric-ep-id': 256, 'fabric-id': 1,
+ 'name': 'sim-ep0'}, 'topology-generation': 18},
+ {'endpoint': {'bus-name': 'platform', 'dev-name': 'fabricsim.1',
+ 'endpoint-id': 1, 'fabric-ep-id': 257, 'fabric-id': 1,
+ 'name': 'sim-ep1'}, 'topology-generation': 18}]
+
Query a single port:
.. code-block:: bash
@@ -310,4 +325,29 @@ Query a single port:
--spec Documentation/netlink/specs/drm_fabric.yaml \
--do port-get --json '{"endpoint-id": 1, "port-index": 0}'
+.. code-block:: text
+
+ {'port': {'endpoint-id': 1, 'max-lane-count': 4,
+ 'max-lane-signaling-rate-mbps': 200000, 'oper-state': 'active',
+ 'peer': {'peer-id': 256, 'port-index': 0, 'type': 'accel'},
+ 'port-index': 0},
+ 'topology-generation': 18}
+
The family name on the wire is ``drm-fabric``.
+
+Synthetic provider
+==================
+
+``CONFIG_DRM_FABRIC_SIM`` builds ``drm-fabric-sim.ko``, a software-only provider
+modeled on netdevsim (Documentation/networking/devlink/netdevsim.rst) that drives
+the object model and uAPI without real hardware. Module parameters select a
+linear, mesh or switch-shaped topology and bound the number of endpoints and
+ports. The switch shape links every endpoint to an opaque switch peer
+(``peer-type = switch``) whose id does not resolve to an endpoint, exercising the
+directed half-edge model without a first-class switch object.
+
+Its debugfs knobs stimulate synthetic counter activity, operational-state changes
+and runtime endpoint add/remove. These files are unstable test controls and are
+not part of the uAPI; the stable, reviewed interface is the YAML-described
+Generic Netlink family. Tests mutate simulator state through debugfs and observe
+the result over Generic Netlink.
diff --git a/drivers/gpu/drm/fabric/Kconfig b/drivers/gpu/drm/fabric/Kconfig
index 7e9c569fd258..87115356baca 100644
--- a/drivers/gpu/drm/fabric/Kconfig
+++ b/drivers/gpu/drm/fabric/Kconfig
@@ -12,3 +12,13 @@ config DRM_FABRIC
called drm-fabric.
If in doubt, say N.
+
+config DRM_FABRIC_SIM
+ tristate "DRM fabric synthetic provider (test-only)"
+ depends on DRM_FABRIC
+ help
+ Synthetic provider for testing drm_fabric topology and ABI.
+ Provides debugfs-only hooks for synthetic activity generation,
+ error injection, and port-state transitions. These hooks are
+ NOT part of the drm_fabric uAPI and are used only by selftests.
+ Does not model UALink protocol traffic or memory semantics.
diff --git a/drivers/gpu/drm/fabric/Makefile b/drivers/gpu/drm/fabric/Makefile
index cf9d9d6be3d3..bc0a6c742164 100644
--- a/drivers/gpu/drm/fabric/Makefile
+++ b/drivers/gpu/drm/fabric/Makefile
@@ -2,3 +2,6 @@
obj-$(CONFIG_DRM_FABRIC) += drm-fabric.o
drm-fabric-y := drm_fabric.o drm_fabric_netlink.o drm_fabric_nl.o
+
+obj-$(CONFIG_DRM_FABRIC_SIM) += drm-fabric-sim.o
+drm-fabric-sim-y := drm_fabric_sim.o
diff --git a/drivers/gpu/drm/fabric/drm_fabric_sim.c b/drivers/gpu/drm/fabric/drm_fabric_sim.c
new file mode 100644
index 000000000000..7d489c6894bf
--- /dev/null
+++ b/drivers/gpu/drm/fabric/drm_fabric_sim.c
@@ -0,0 +1,978 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright © 2026 Intel Corporation
+ */
+
+#include <linux/cleanup.h>
+#include <linux/debugfs.h>
+#include <linux/err.h>
+#include <linux/kstrtox.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/timer.h>
+
+#include <drm/drm_fabric.h>
+#include <uapi/drm/drm_fabric.h>
+
+/**
+ * DOC: fabricsim
+ *
+ * In-kernel synthetic drm_fabric provider for review and CI. It registers a
+ * linear, mesh or switch topology and drives the selftests through debugfs:
+ * synthetic counters, port-state transitions, endpoint hotplug, a bulk
+ * population for dump-resume testing, and fault injection. The debugfs hooks
+ * are not uAPI; userspace observes the resulting state through the Generic
+ * Netlink ABI.
+ */
+
+static char *topology = "mesh";
+module_param(topology, charp, 0444);
+MODULE_PARM_DESC(topology, "Topology shape: linear, mesh, switch (default: mesh)");
+
+static int num_endpoints = 4;
+module_param(num_endpoints, int, 0444);
+MODULE_PARM_DESC(num_endpoints, "Number of endpoints (2-8, default: 4)");
+
+static int ports_per_ep = 4;
+module_param(ports_per_ep, int, 0444);
+MODULE_PARM_DESC(ports_per_ep, "Ports per endpoint (1-16, default: 4)");
+
+struct fabricsim_port_priv {
+ struct drm_fabric_port *port;
+
+ /* Bumped lockless from the timer and debugfs; untorn on 32-bit. */
+ atomic64_t read_bytes;
+ atomic64_t write_bytes;
+ atomic64_t link_down_count;
+ atomic64_t retrain_count;
+
+ /* Blocks enable while disable drains a pending timer re-arm. */
+ struct mutex activity_lock;
+ bool activity_enabled;
+ struct timer_list activity_timer;
+ u32 read_rate; /* bytes per timer tick (FABRICSIM_TICK_MS) */
+ u32 write_rate; /* bytes per timer tick (FABRICSIM_TICK_MS) */
+
+ /*
+ * Test-only: returns -stats_errno, with values above MAX_ERRNO mapped
+ * to -EIO.
+ */
+ u32 stats_errno;
+};
+
+struct fabricsim_ep_priv {
+ struct drm_fabric_endpoint *ep;
+ struct platform_device *pdev; /* backing device for dev-name/bus-name */
+ struct fabricsim_port_priv *ports;
+ int num_ports;
+ int slot;
+ bool runtime;
+ struct dentry *dbg_dir;
+};
+
+#define FABRICSIM_MAX_EPS 512
+/* Initial generated topology only; runtime hotplug uses FABRICSIM_MAX_EPS. */
+#define FABRICSIM_MAX_INIT_EPS 8
+
+static struct drm_fabric *fabricsim_fabric;
+static struct fabricsim_ep_priv *fabricsim_slots[FABRICSIM_MAX_EPS];
+static int fabricsim_init_eps;
+static bool fabricsim_exiting; /* gate runtime controls during teardown */
+/*
+ * Nests outside drm_fabric_lock and is never taken from the provider ops or the
+ * debugfs port handlers, so the two cannot invert.
+ */
+static DEFINE_MUTEX(fabricsim_lock);
+static struct dentry *fabricsim_debugfs_root;
+
+/* Test-only fault injection (debugfs). Sticky until cleared. */
+static bool fabricsim_fail_register;
+static u32 fabricsim_fail_errno = ENOMEM;
+
+/*
+ * Negative errno an armed fault returns; zero or an out-of-range value
+ * gives -ENOMEM.
+ */
+static int fabricsim_injected_errno(void)
+{
+ u32 e = fabricsim_fail_errno;
+
+ if (e == 0 || e > MAX_ERRNO)
+ return -ENOMEM;
+ return -(int)e;
+}
+
+static int fabricsim_port_stats_get(struct drm_fabric_port *port,
+ struct drm_fabric_port_stats *stats)
+{
+ struct fabricsim_ep_priv *ep_priv = port->endpoint->priv;
+ struct fabricsim_port_priv *pp;
+
+ /*
+ * The endpoint is pinned by the core; missing private state is a
+ * provider bug.
+ */
+ if (!ep_priv)
+ return -ENOENT;
+
+ /*
+ * Port i has index i. Index rather than search for a matching ->port,
+ * which a dump racing registration would not yet see.
+ */
+ if (port->index >= (u32)ep_priv->num_ports)
+ return -ENOENT;
+
+ pp = &ep_priv->ports[port->index];
+
+ /* Dumps skip -EOPNOTSUPP; any other injected error aborts the dump. */
+ if (pp->stats_errno) {
+ u32 e = pp->stats_errno;
+
+ return e <= MAX_ERRNO ? -(int)e : -EIO;
+ }
+
+ stats->read_bytes = atomic64_read(&pp->read_bytes);
+ stats->write_bytes = atomic64_read(&pp->write_bytes);
+ stats->link_down_count = atomic64_read(&pp->link_down_count);
+ stats->retrain_count = atomic64_read(&pp->retrain_count);
+
+ return 0;
+}
+
+static const struct drm_fabric_ops fabricsim_ops = {
+ .port_stats_get = fabricsim_port_stats_get,
+};
+
+#define FABRICSIM_TICK_MS 100
+
+static void fabricsim_activity_tick(struct timer_list *t)
+{
+ struct fabricsim_port_priv *pp =
+ container_of(t, struct fabricsim_port_priv, activity_timer);
+
+ /*
+ * Paired with WRITE_ONCE() in the enable/disable path, so a stopped
+ * timer stays stopped.
+ */
+ if (!READ_ONCE(pp->activity_enabled))
+ return;
+
+ atomic64_add(READ_ONCE(pp->read_rate), &pp->read_bytes);
+ atomic64_add(READ_ONCE(pp->write_rate), &pp->write_bytes);
+
+ mod_timer(&pp->activity_timer,
+ jiffies + msecs_to_jiffies(FABRICSIM_TICK_MS));
+}
+
+static ssize_t fabricsim_activity_write(struct file *file,
+ const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct fabricsim_port_priv *pp = file->private_data;
+ char kbuf[8];
+ int val;
+
+ if (count >= sizeof(kbuf))
+ return -EINVAL;
+ if (copy_from_user(kbuf, buf, count))
+ return -EFAULT;
+ kbuf[count] = '\0';
+
+ if (kstrtoint(kbuf, 10, &val))
+ return -EINVAL;
+
+ /*
+ * Held across the whole transition, or a concurrent enable could re-arm
+ * mid-drain.
+ */
+ scoped_guard(mutex, &pp->activity_lock) {
+ if (val && !pp->activity_enabled) {
+ WRITE_ONCE(pp->activity_enabled, true);
+ mod_timer(&pp->activity_timer,
+ jiffies + msecs_to_jiffies(FABRICSIM_TICK_MS));
+ } else if (!val && pp->activity_enabled) {
+ WRITE_ONCE(pp->activity_enabled, false);
+ /*
+ * Not timer_shutdown_sync(): a later enable re-arms.
+ * A tick past the enabled check can re-arm after one
+ * timer_delete_sync(), so loop until a drain is clean.
+ */
+ while (timer_delete_sync(&pp->activity_timer))
+ ;
+ }
+ }
+
+ return count;
+}
+
+static ssize_t fabricsim_activity_read(struct file *file,
+ char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct fabricsim_port_priv *pp = file->private_data;
+ char kbuf[4];
+ int len;
+
+ len = scnprintf(kbuf, sizeof(kbuf), "%d\n",
+ READ_ONCE(pp->activity_enabled) ? 1 : 0);
+ return simple_read_from_buffer(buf, count, ppos, kbuf, len);
+}
+
+/* Per-port debugfs files use fabricsim_port_priv as file->private_data. */
+static const struct file_operations fabricsim_activity_fops = {
+ .owner = THIS_MODULE,
+ .open = simple_open,
+ .read = fabricsim_activity_read,
+ .write = fabricsim_activity_write,
+};
+
+static ssize_t fabricsim_inject_write(struct file *file,
+ const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct fabricsim_port_priv *pp = file->private_data;
+ char kbuf[32];
+
+ if (count >= sizeof(kbuf))
+ return -EINVAL;
+ if (copy_from_user(kbuf, buf, count))
+ return -EFAULT;
+ kbuf[count] = '\0';
+ if (count > 0 && kbuf[count - 1] == '\n')
+ kbuf[count - 1] = '\0';
+
+ if (strcmp(kbuf, "link_down") == 0) {
+ atomic64_inc(&pp->link_down_count);
+ drm_fabric_port_set_oper(pp->port, DRM_FABRIC_PORT_STATE_INACTIVE);
+ } else if (strcmp(kbuf, "degrade") == 0) {
+ drm_fabric_port_set_oper(pp->port, DRM_FABRIC_PORT_STATE_DEGRADED);
+ } else if (strcmp(kbuf, "recover_to_active") == 0) {
+ atomic64_inc(&pp->retrain_count);
+ drm_fabric_port_set_oper(pp->port, DRM_FABRIC_PORT_STATE_ACTIVE);
+ } else {
+ return -EINVAL;
+ }
+
+ return count;
+}
+
+static const struct file_operations fabricsim_inject_fops = {
+ .owner = THIS_MODULE,
+ .open = simple_open,
+ .write = fabricsim_inject_write,
+};
+
+static ssize_t fabricsim_oper_state_write(struct file *file,
+ const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct fabricsim_port_priv *pp = file->private_data;
+ char kbuf[16];
+
+ if (count >= sizeof(kbuf))
+ return -EINVAL;
+ if (copy_from_user(kbuf, buf, count))
+ return -EFAULT;
+ kbuf[count] = '\0';
+ if (count > 0 && kbuf[count - 1] == '\n')
+ kbuf[count - 1] = '\0';
+
+ if (strcmp(kbuf, "unknown") == 0)
+ drm_fabric_port_set_oper(pp->port, DRM_FABRIC_PORT_STATE_UNKNOWN);
+ else if (strcmp(kbuf, "inactive") == 0)
+ drm_fabric_port_set_oper(pp->port, DRM_FABRIC_PORT_STATE_INACTIVE);
+ else if (strcmp(kbuf, "active") == 0)
+ drm_fabric_port_set_oper(pp->port, DRM_FABRIC_PORT_STATE_ACTIVE);
+ else if (strcmp(kbuf, "degraded") == 0)
+ drm_fabric_port_set_oper(pp->port, DRM_FABRIC_PORT_STATE_DEGRADED);
+ else
+ return -EINVAL;
+
+ return count;
+}
+
+static ssize_t fabricsim_oper_state_read(struct file *file,
+ char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct fabricsim_port_priv *pp = file->private_data;
+ const char *state_str;
+ char kbuf[16];
+ int len;
+
+ switch (pp->port->oper_state) {
+ case DRM_FABRIC_PORT_STATE_INACTIVE:
+ state_str = "inactive";
+ break;
+ case DRM_FABRIC_PORT_STATE_ACTIVE:
+ state_str = "active";
+ break;
+ case DRM_FABRIC_PORT_STATE_DEGRADED:
+ state_str = "degraded";
+ break;
+ default:
+ state_str = "unknown";
+ break;
+ }
+
+ len = scnprintf(kbuf, sizeof(kbuf), "%s\n", state_str);
+ return simple_read_from_buffer(buf, count, ppos, kbuf, len);
+}
+
+static const struct file_operations fabricsim_oper_state_fops = {
+ .owner = THIS_MODULE,
+ .open = simple_open,
+ .read = fabricsim_oper_state_read,
+ .write = fabricsim_oper_state_write,
+};
+
+static void fabricsim_link_linear(void)
+{
+ int i;
+ int port_cursor[FABRICSIM_MAX_INIT_EPS] = {0};
+ struct drm_fabric_peer peer;
+
+ /* Linear chain: ep[0]<->ep[1]<->ep[2]<->...<->ep[N-1] */
+ for (i = 0; i < fabricsim_init_eps - 1; i++) {
+ int pa_idx = port_cursor[i]++;
+ int pb_idx = port_cursor[i + 1]++;
+ struct drm_fabric_endpoint *ep_a = fabricsim_slots[i]->ep;
+ struct drm_fabric_endpoint *ep_b = fabricsim_slots[i + 1]->ep;
+ struct drm_fabric_port *pa, *pb;
+
+ /*
+ * Interior nodes consume two ports; stop rather than walk off
+ * an endpoint's port array if it was sized too small.
+ */
+ if (pa_idx >= fabricsim_slots[i]->num_ports ||
+ pb_idx >= fabricsim_slots[i + 1]->num_ports)
+ break;
+
+ pa = fabricsim_slots[i]->ports[pa_idx].port;
+ pb = fabricsim_slots[i + 1]->ports[pb_idx].port;
+
+ /* Peers are directed half-edges, so install both directions. */
+ peer.peer_id = ep_b->fabric_ep_id;
+ peer.peer_type = DRM_FABRIC_PEER_TYPE_ACCEL;
+ peer.port_index = pb->index;
+ drm_fabric_port_set_peer(pa, &peer);
+
+ peer.peer_id = ep_a->fabric_ep_id;
+ peer.peer_type = DRM_FABRIC_PEER_TYPE_ACCEL;
+ peer.port_index = pa->index;
+ drm_fabric_port_set_peer(pb, &peer);
+ }
+}
+
+static void fabricsim_link_mesh(void)
+{
+ int i, j, k, port_idx, peer_port_idx;
+ struct drm_fabric_peer peer;
+
+ /* Fully-connected K_N: port j on ep[i] reaches ep[j], shifted past i. */
+ for (i = 0; i < fabricsim_init_eps; i++) {
+ port_idx = 0;
+ for (j = 0; j < fabricsim_init_eps; j++) {
+ if (i == j)
+ continue;
+
+ if (port_idx >= fabricsim_slots[i]->num_ports)
+ break;
+
+ /*
+ * On endpoint j, i uses the slot obtained by skipping j
+ * in endpoint order.
+ */
+ peer_port_idx = 0;
+ for (k = 0; k < fabricsim_init_eps; k++) {
+ if (k == j)
+ continue;
+ if (k == i)
+ break;
+ peer_port_idx++;
+ }
+
+ peer.peer_id = fabricsim_slots[j]->ep->fabric_ep_id;
+ peer.peer_type = DRM_FABRIC_PEER_TYPE_ACCEL;
+ peer.port_index = peer_port_idx;
+ drm_fabric_port_set_peer(fabricsim_slots[i]->ports[port_idx].port,
+ &peer);
+
+ port_idx++;
+ }
+ }
+}
+
+/*
+ * Opaque switch peer-id, deliberately outside the leaf range (0x100 + slot)
+ * so it never resolves in the endpoint registry.
+ */
+#define FABRICSIM_SWITCH_FABRIC_EP_ID 0x5000ULL
+
+static void fabricsim_link_switch(void)
+{
+ struct drm_fabric_peer peer;
+ int i;
+
+ for (i = 0; i < fabricsim_init_eps; i++) {
+ struct drm_fabric_port *leaf_port =
+ fabricsim_slots[i]->ports[0].port;
+
+ if (!leaf_port)
+ continue;
+
+ /* One directed half-edge from the leaf to an opaque switch. */
+ peer.peer_id = FABRICSIM_SWITCH_FABRIC_EP_ID;
+ peer.peer_type = DRM_FABRIC_PEER_TYPE_SWITCH;
+ peer.port_index = i; /* distinct switch-side port per leaf */
+ drm_fabric_port_set_peer(leaf_port, &peer);
+ }
+}
+
+static void fabricsim_ep_debugfs_create(struct fabricsim_ep_priv *ep_priv)
+{
+ struct dentry *port_dir;
+ char name[32];
+ int j;
+
+ if (IS_ERR_OR_NULL(fabricsim_debugfs_root))
+ return;
+
+ snprintf(name, sizeof(name), "ep%d", ep_priv->slot);
+ ep_priv->dbg_dir = debugfs_create_dir(name, fabricsim_debugfs_root);
+ if (IS_ERR_OR_NULL(ep_priv->dbg_dir)) {
+ ep_priv->dbg_dir = NULL;
+ return;
+ }
+
+ for (j = 0; j < ep_priv->num_ports; j++) {
+ struct fabricsim_port_priv *pp = &ep_priv->ports[j];
+
+ snprintf(name, sizeof(name), "port%d", j);
+ port_dir = debugfs_create_dir(name, ep_priv->dbg_dir);
+
+ debugfs_create_file("activity_enable", 0644,
+ port_dir, pp, &fabricsim_activity_fops);
+ debugfs_create_file("inject", 0200,
+ port_dir, pp, &fabricsim_inject_fops);
+ debugfs_create_file("oper_state", 0644,
+ port_dir, pp, &fabricsim_oper_state_fops);
+ debugfs_create_u32("read_rate", 0644, port_dir, &pp->read_rate);
+ debugfs_create_u32("write_rate", 0644, port_dir, &pp->write_rate);
+ debugfs_create_u32("stats_errno", 0644, port_dir, &pp->stats_errno);
+ }
+}
+
+/*
+ * Create one endpoint at @slot with @nports ports, registered as a member of
+ * the synthetic fabric. Returns the new ep_priv or an ERR_PTR. Caller holds
+ * fabricsim_lock.
+ */
+static struct fabricsim_ep_priv *fabricsim_make_ep(int slot, int nports)
+{
+ struct drm_fabric_endpoint_desc edesc = {};
+ struct drm_fabric_port_desc pdescs[16];
+ struct fabricsim_ep_priv *ep_priv;
+ struct platform_device *pdev;
+ char ep_name[32];
+ int j, ret;
+
+ lockdep_assert_held(&fabricsim_lock);
+
+ if (nports < 1)
+ nports = 1;
+ if (nports > 16)
+ nports = 16;
+
+ /* Refuse before any allocation, so there is nothing to roll back. */
+ if (fabricsim_fail_register)
+ return ERR_PTR(fabricsim_injected_errno());
+
+ ep_priv = kzalloc_obj(*ep_priv, GFP_KERNEL);
+ if (!ep_priv)
+ return ERR_PTR(-ENOMEM);
+
+ ep_priv->slot = slot;
+ ep_priv->num_ports = nports;
+
+ pdev = platform_device_register_simple("fabricsim", slot, NULL, 0);
+ if (IS_ERR(pdev)) {
+ ret = PTR_ERR(pdev);
+ goto err_free;
+ }
+ ep_priv->pdev = pdev;
+
+ for (j = 0; j < nports; j++) {
+ pdescs[j].index = j;
+ pdescs[j].max_lane_count = 4;
+ pdescs[j].max_lane_signaling_rate_mbps = 200000; /* 200 Gbps/lane */
+ }
+
+ snprintf(ep_name, sizeof(ep_name), "sim-ep%d", slot);
+ edesc.fabric_ep_id = 0x100 + slot;
+ edesc.name = ep_name;
+ edesc.parent = &pdev->dev;
+ edesc.ops = &fabricsim_ops;
+ edesc.priv = ep_priv;
+ edesc.ports = pdescs;
+ edesc.num_ports = nports;
+
+ /*
+ * Fill the port array before registering: a racing PORT_STATS_GET can
+ * hit any port once published.
+ */
+ ep_priv->ports = kcalloc(nports, sizeof(struct fabricsim_port_priv),
+ GFP_KERNEL);
+ if (!ep_priv->ports) {
+ ret = -ENOMEM;
+ goto err_pdev;
+ }
+
+ for (j = 0; j < nports; j++) {
+ struct fabricsim_port_priv *pp = &ep_priv->ports[j];
+
+ pp->read_rate = 1024;
+ pp->write_rate = 512;
+ mutex_init(&pp->activity_lock);
+ timer_setup(&pp->activity_timer, fabricsim_activity_tick, 0);
+ }
+
+ ep_priv->ep = drm_fabric_endpoint_register(fabricsim_fabric, &edesc);
+ if (IS_ERR(ep_priv->ep)) {
+ ret = PTR_ERR(ep_priv->ep);
+ goto err_ports;
+ }
+
+ /*
+ * Fill the simulator port pointers before topology wiring and before
+ * the debugfs nodes make them externally reachable.
+ */
+ for (j = 0; j < nports; j++)
+ ep_priv->ports[j].port = drm_fabric_endpoint_port(ep_priv->ep, j);
+
+ fabricsim_ep_debugfs_create(ep_priv);
+
+ return ep_priv;
+
+err_ports:
+ for (j = 0; j < nports; j++)
+ mutex_destroy(&ep_priv->ports[j].activity_lock);
+ kfree(ep_priv->ports);
+err_pdev:
+ platform_device_unregister(ep_priv->pdev);
+err_free:
+ kfree(ep_priv);
+ return ERR_PTR(ret);
+}
+
+/* Peers on other endpoints are left untouched. Caller holds fabricsim_lock. */
+static void fabricsim_destroy_ep(struct fabricsim_ep_priv *ep_priv)
+{
+ int j;
+
+ lockdep_assert_held(&fabricsim_lock);
+
+ debugfs_remove_recursive(ep_priv->dbg_dir);
+
+ for (j = 0; j < ep_priv->num_ports; j++) {
+ struct fabricsim_port_priv *pp = &ep_priv->ports[j];
+
+ /*
+ * debugfs_remove_recursive() drained writers before the
+ * activity timer is shut down.
+ */
+ scoped_guard(mutex, &pp->activity_lock) {
+ WRITE_ONCE(pp->activity_enabled, false);
+ timer_shutdown_sync(&pp->activity_timer);
+ }
+ mutex_destroy(&pp->activity_lock);
+ }
+
+ drm_fabric_endpoint_unregister(ep_priv->ep);
+ kfree(ep_priv->ports);
+ platform_device_unregister(ep_priv->pdev);
+ kfree(ep_priv);
+}
+
+static int fabricsim_add_endpoint(int nports)
+{
+ struct fabricsim_ep_priv *ep_priv;
+ int slot, ret;
+
+ mutex_lock(&fabricsim_lock);
+ if (fabricsim_exiting) {
+ mutex_unlock(&fabricsim_lock);
+ return -ENODEV;
+ }
+
+ for (slot = 0; slot < FABRICSIM_MAX_EPS; slot++)
+ if (!fabricsim_slots[slot])
+ break;
+ if (slot == FABRICSIM_MAX_EPS) {
+ mutex_unlock(&fabricsim_lock);
+ return -ENOSPC;
+ }
+
+ ep_priv = fabricsim_make_ep(slot, nports);
+ if (IS_ERR(ep_priv)) {
+ ret = PTR_ERR(ep_priv);
+ mutex_unlock(&fabricsim_lock);
+ return ret;
+ }
+ ep_priv->runtime = true;
+ fabricsim_slots[slot] = ep_priv;
+ mutex_unlock(&fabricsim_lock);
+
+ return slot;
+}
+
+/* fabricsim_lock stays held across teardown, so the slot remains reserved. */
+static int fabricsim_del_endpoint(int slot)
+{
+ struct fabricsim_ep_priv *ep_priv;
+
+ if (slot < 0 || slot >= FABRICSIM_MAX_EPS)
+ return -EINVAL;
+
+ mutex_lock(&fabricsim_lock);
+ if (fabricsim_exiting) {
+ mutex_unlock(&fabricsim_lock);
+ return -ENODEV;
+ }
+ ep_priv = fabricsim_slots[slot];
+ if (!ep_priv) {
+ mutex_unlock(&fabricsim_lock);
+ return -ENOENT;
+ }
+ fabricsim_destroy_ep(ep_priv);
+ fabricsim_slots[slot] = NULL;
+ mutex_unlock(&fabricsim_lock);
+
+ return 0;
+}
+
+/*
+ * @n single-port member endpoints for the dump-scale selftest: population, not
+ * topology. Returns the count added, or a negative errno only if none were.
+ */
+static int fabricsim_bulk_add(int n)
+{
+ int added = 0;
+ int ret;
+
+ if (n <= 0)
+ return -EINVAL;
+
+ while (added < n) {
+ ret = fabricsim_add_endpoint(1);
+ if (ret < 0)
+ return added ? added : ret;
+ added++;
+ }
+ return added;
+}
+
+/*
+ * Ownership comes from ->runtime, not the slot index: slots are reused, so a
+ * runtime endpoint can sit below the init population.
+ */
+static int fabricsim_bulk_del(void)
+{
+ int slot;
+
+ for (slot = 0; slot < FABRICSIM_MAX_EPS; slot++) {
+ mutex_lock(&fabricsim_lock);
+ if (fabricsim_exiting) {
+ mutex_unlock(&fabricsim_lock);
+ return -ENODEV;
+ }
+ if (fabricsim_slots[slot] && fabricsim_slots[slot]->runtime) {
+ fabricsim_destroy_ep(fabricsim_slots[slot]);
+ fabricsim_slots[slot] = NULL;
+ }
+ mutex_unlock(&fabricsim_lock);
+ }
+ return 0;
+}
+
+static int fabricsim_parse_int(const char __user *buf, size_t count, int dflt)
+{
+ char kbuf[16];
+ int val;
+
+ if (count == 0 || count >= sizeof(kbuf))
+ return dflt;
+ if (copy_from_user(kbuf, buf, count))
+ return dflt;
+ kbuf[count] = '\0';
+ if (kstrtoint(strim(kbuf), 10, &val))
+ return dflt;
+ return val;
+}
+
+static ssize_t fabricsim_add_ep_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ int nports = fabricsim_parse_int(buf, count, ports_per_ep);
+ int ret = fabricsim_add_endpoint(nports);
+
+ return ret < 0 ? ret : count;
+}
+
+static ssize_t fabricsim_del_ep_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ int slot = fabricsim_parse_int(buf, count, -1);
+ int ret = fabricsim_del_endpoint(slot);
+
+ return ret < 0 ? ret : count;
+}
+
+static const struct file_operations fabricsim_add_ep_fops = {
+ .owner = THIS_MODULE,
+ .write = fabricsim_add_ep_write,
+};
+
+static const struct file_operations fabricsim_del_ep_fops = {
+ .owner = THIS_MODULE,
+ .write = fabricsim_del_ep_write,
+};
+
+static ssize_t fabricsim_bulk_add_write(struct file *file,
+ const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ int n = fabricsim_parse_int(buf, count, 0);
+ int ret = fabricsim_bulk_add(n);
+
+ return ret < 0 ? ret : count;
+}
+
+static ssize_t fabricsim_bulk_del_write(struct file *file,
+ const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ int ret = fabricsim_bulk_del();
+
+ return ret < 0 ? ret : count;
+}
+
+static const struct file_operations fabricsim_bulk_add_fops = {
+ .owner = THIS_MODULE,
+ .write = fabricsim_bulk_add_write,
+};
+
+static const struct file_operations fabricsim_bulk_del_fops = {
+ .owner = THIS_MODULE,
+ .write = fabricsim_bulk_del_write,
+};
+
+static ssize_t fabricsim_fail_errno_write(struct file *file,
+ const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ u32 val;
+ int ret;
+
+ ret = kstrtou32_from_user(buf, count, 0, &val);
+ if (ret)
+ return ret;
+ /* Zero stays the documented "clear to -ENOMEM" sentinel. */
+ if (val > MAX_ERRNO)
+ return -EINVAL;
+ fabricsim_fail_errno = val;
+ return count;
+}
+
+static ssize_t fabricsim_fail_errno_read(struct file *file, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ char kbuf[16];
+ int len;
+
+ len = scnprintf(kbuf, sizeof(kbuf), "%u\n", fabricsim_fail_errno);
+ return simple_read_from_buffer(buf, count, ppos, kbuf, len);
+}
+
+static const struct file_operations fabricsim_fail_errno_fops = {
+ .owner = THIS_MODULE,
+ .open = simple_open,
+ .read = fabricsim_fail_errno_read,
+ .write = fabricsim_fail_errno_write,
+};
+
+/*
+ * Validate and derive the module parameters before anything is registered.
+ * This runs before allocation, so it needs no unwind path.
+ */
+static int __init fabricsim_setup_params(void)
+{
+ /*
+ * Reject an unrecognised topology rather than falling back to mesh, so
+ * a typo cannot fake a shape.
+ */
+ if (strcmp(topology, "mesh") && strcmp(topology, "linear") &&
+ strcmp(topology, "switch")) {
+ pr_err("fabricsim: unknown topology \"%s\" (use mesh, linear or switch)\n",
+ topology);
+ return -EINVAL;
+ }
+
+ if (num_endpoints < 2)
+ num_endpoints = 2;
+ if (num_endpoints > FABRICSIM_MAX_INIT_EPS)
+ num_endpoints = FABRICSIM_MAX_INIT_EPS;
+ if (ports_per_ep < 1)
+ ports_per_ep = 1;
+ if (ports_per_ep > 16)
+ ports_per_ep = 16;
+
+ /*
+ * A mesh gives every endpoint (N-1) peers, so the busiest endpoint needs
+ * at least (N-1) ports. The switch shape only needs one port per leaf
+ * (a single half-edge to the opaque switch), so it is not bumped here.
+ */
+ if (strcmp(topology, "mesh") == 0 && ports_per_ep < num_endpoints - 1)
+ ports_per_ep = num_endpoints - 1;
+
+ /*
+ * A linear chain gives every interior node two neighbours, so it needs
+ * at least two ports; bump a too-small request rather than index past
+ * the endpoint's port array.
+ */
+ if (strcmp(topology, "linear") == 0 && num_endpoints > 2 &&
+ ports_per_ep < 2)
+ ports_per_ep = 2;
+
+ fabricsim_init_eps = num_endpoints;
+
+ return 0;
+}
+
+static int __init fabricsim_init(void)
+{
+ struct drm_fabric_desc fdesc;
+ int i, j, ret;
+
+ ret = fabricsim_setup_params();
+ if (ret)
+ return ret;
+
+ fdesc.type = DRM_FABRIC_TYPE_SYNTHETIC;
+ fdesc.name = "fabricsim";
+ fdesc.instance_id = 0x8086CAFE;
+
+ fabricsim_fabric = drm_fabric_register(&fdesc);
+ if (IS_ERR(fabricsim_fabric))
+ return PTR_ERR(fabricsim_fabric);
+
+ /* Root must exist before the per-endpoint debugfs subtrees. */
+ fabricsim_debugfs_root = debugfs_create_dir("drm_fabric_sim", NULL);
+ if (IS_ERR(fabricsim_debugfs_root))
+ fabricsim_debugfs_root = NULL;
+
+ mutex_lock(&fabricsim_lock);
+ for (i = 0; i < fabricsim_init_eps; i++) {
+ struct fabricsim_ep_priv *ep_priv =
+ fabricsim_make_ep(i, ports_per_ep);
+
+ if (IS_ERR(ep_priv)) {
+ ret = PTR_ERR(ep_priv);
+ mutex_unlock(&fabricsim_lock);
+ goto err_eps;
+ }
+ fabricsim_slots[i] = ep_priv;
+ }
+ mutex_unlock(&fabricsim_lock);
+
+ if (strcmp(topology, "linear") == 0)
+ fabricsim_link_linear();
+ else if (strcmp(topology, "switch") == 0)
+ fabricsim_link_switch();
+ else
+ fabricsim_link_mesh();
+
+ /* A linked port starts ACTIVE; an unlinked one stays as registered. */
+ for (i = 0; i < fabricsim_init_eps; i++) {
+ for (j = 0; j < fabricsim_slots[i]->num_ports; j++) {
+ struct fabricsim_port_priv *pp = &fabricsim_slots[i]->ports[j];
+
+ if (pp->port && pp->port->has_peer)
+ drm_fabric_port_set_oper(pp->port,
+ DRM_FABRIC_PORT_STATE_ACTIVE);
+ }
+ }
+
+ /* Root-level runtime lifecycle controls (test-only, not uAPI). */
+ if (fabricsim_debugfs_root) {
+ debugfs_create_file("add_endpoint", 0200, fabricsim_debugfs_root,
+ NULL, &fabricsim_add_ep_fops);
+ debugfs_create_file("del_endpoint", 0200, fabricsim_debugfs_root,
+ NULL, &fabricsim_del_ep_fops);
+
+ debugfs_create_file("bulk_add", 0200, fabricsim_debugfs_root,
+ NULL, &fabricsim_bulk_add_fops);
+ debugfs_create_file("bulk_del", 0200, fabricsim_debugfs_root,
+ NULL, &fabricsim_bulk_del_fops);
+
+ debugfs_create_bool("fail_register", 0644,
+ fabricsim_debugfs_root,
+ &fabricsim_fail_register);
+ debugfs_create_file("fail_errno", 0644,
+ fabricsim_debugfs_root,
+ NULL, &fabricsim_fail_errno_fops);
+ }
+
+ pr_info("fabricsim: registered %s topology with %d endpoints, %d ports/ep\n",
+ topology, fabricsim_init_eps, ports_per_ep);
+
+ return 0;
+
+err_eps:
+ mutex_lock(&fabricsim_lock);
+ for (i = FABRICSIM_MAX_EPS - 1; i >= 0; i--) {
+ if (fabricsim_slots[i]) {
+ fabricsim_destroy_ep(fabricsim_slots[i]);
+ fabricsim_slots[i] = NULL;
+ }
+ }
+ mutex_unlock(&fabricsim_lock);
+ debugfs_remove_recursive(fabricsim_debugfs_root);
+ WARN_ON(drm_fabric_unregister(fabricsim_fabric));
+ return ret;
+}
+
+static void __exit fabricsim_exit(void)
+{
+ int i;
+
+ /*
+ * fabricsim_exiting gates add/del first and the root subtree is removed
+ * last, so no handler can race this teardown.
+ */
+ mutex_lock(&fabricsim_lock);
+ fabricsim_exiting = true;
+ for (i = FABRICSIM_MAX_EPS - 1; i >= 0; i--) {
+ if (fabricsim_slots[i]) {
+ fabricsim_destroy_ep(fabricsim_slots[i]);
+ fabricsim_slots[i] = NULL;
+ }
+ }
+ mutex_unlock(&fabricsim_lock);
+
+ debugfs_remove_recursive(fabricsim_debugfs_root);
+ WARN_ON(drm_fabric_unregister(fabricsim_fabric));
+
+ pr_info("fabricsim: unloaded\n");
+}
+
+module_init(fabricsim_init);
+module_exit(fabricsim_exit);
+
+MODULE_AUTHOR("Intel Corporation");
+MODULE_AUTHOR("Konstantin Sinyuk <ksinyuk@kernel.org>");
+MODULE_DESCRIPTION("DRM Fabric fabricsim synthetic driver");
+MODULE_LICENSE("Dual MIT/GPL");
--
2.43.0
next prev parent reply other threads:[~2026-08-24 8:09 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-24 8:09 [RFC PATCH 0/12] drm/fabric: vendor-neutral topology infrastructure for scale-up accelerator interconnects Konstantin Sinyuk
2026-08-24 8:09 ` [RFC PATCH 01/12] drm/fabric: add core object model and provider API Konstantin Sinyuk
2026-08-24 8:09 ` [RFC PATCH 02/12] drm/fabric: add query uAPI and generated headers Konstantin Sinyuk
2026-08-24 8:09 ` [RFC PATCH 03/12] drm/fabric: implement query netlink operations Konstantin Sinyuk
2026-08-24 8:09 ` Konstantin Sinyuk [this message]
2026-08-24 8:09 ` [RFC PATCH 05/12] drm/fabric: add object-model KUnit tests Konstantin Sinyuk
2026-08-24 8:09 ` [RFC PATCH 06/12] drm/fabric: add YNL query and policy selftests Konstantin Sinyuk
2026-08-24 8:09 ` [RFC PATCH 07/12] drm/fabric: add topology-provisioning core Konstantin Sinyuk
2026-08-24 8:09 ` [RFC PATCH 08/12] drm/fabric: add provisioning netlink uAPI Konstantin Sinyuk
2026-08-24 8:09 ` [RFC PATCH 09/12] drm/fabric: implement mutation netlink operations Konstantin Sinyuk
2026-08-24 8:09 ` [RFC PATCH 10/12] drm/fabric: make the synthetic provider writable Konstantin Sinyuk
2026-08-24 8:09 ` [RFC PATCH 11/12] drm/fabric: add mutation KUnit tests Konstantin Sinyuk
2026-08-24 8:09 ` [RFC PATCH 12/12] drm/fabric: add mutation netlink selftests Konstantin Sinyuk
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=e5c0fecf67dbe72f8dd6c49489833ebdf23c5a90.1787552412.git.ksinyuk@kernel.org \
--to=ksinyuk@kernel.org \
--cc=airlied@gmail.com \
--cc=corbet@lwn.net \
--cc=davem@davemloft.net \
--cc=donald.hunter@gmail.com \
--cc=dri-devel@lists.freedesktop.org \
--cc=edumazet@google.com \
--cc=francois.dugast@intel.com \
--cc=horms@kernel.org \
--cc=ilia.levi@intel.com \
--cc=kuba@kernel.org \
--cc=linux-doc@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=maarten.lankhorst@linux.intel.com \
--cc=mripard@kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=rodrigo.vivi@intel.com \
--cc=simona@ffwll.ch \
--cc=skhan@linuxfoundation.org \
--cc=tzimmermann@suse.de \
/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®