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 03/12] drm/fabric: implement query netlink operations
Date: Mon, 24 Aug 2026 11:09:31 +0300 [thread overview]
Message-ID: <fea1fb8ce49d7b673a0ccd9e950cdc0fc0565c68.1787552412.git.ksinyuk@kernel.org> (raw)
In-Reply-To: <cover.1787552412.git.ksinyuk@kernel.org>
Register the drm-fabric Generic Netlink family and connect the four
read-only query commands to the core object model, adding the generated
operation and policy source that dispatches to them. Their reply shapes
are:
fabric-get -> fabric, topology-generation
endpoint-get -> endpoint, topology-generation
port-get -> port, topology-generation
port-stats-get -> port-stats
Support single-object lookup and multipart dumps for fabrics, endpoints and
ports, with optional fabric and endpoint filters. Preserve the nested
endpoint/port dump cursor across batches, including endpoint removal and
terminal port indices.
Port statistics are optional: a targeted unsupported request returns
-EOPNOTSUPP, while a dump skips unsupported ports and continues; other
provider errors terminate the dump. Use topology-generation for dump
consistency and report concurrent changes with NLM_F_DUMP_INTR.
Emit fabric, endpoint, port-state and peer notifications through the
monitor multicast group. Advance topology-generation before emitting each
notification, so the event carries the same post-change value as a later
query.
Queries and notifications are confined to init_net. The query commands,
notifications and dump-consistency behavior are documented in
Documentation/gpu/drm-fabric.rst.
Co-developed-by: Ilia Levi <ilia.levi@intel.com>
Signed-off-by: Ilia Levi <ilia.levi@intel.com>
Signed-off-by: Konstantin Sinyuk <ksinyuk@kernel.org>
Assisted-by: GitHub-Copilot:claude-opus-4.8
---
Documentation/gpu/drm-fabric.rst | 148 ++-
drivers/gpu/drm/fabric/Kconfig | 6 +-
drivers/gpu/drm/fabric/Makefile | 2 +-
drivers/gpu/drm/fabric/drm_fabric.c | 29 +-
drivers/gpu/drm/fabric/drm_fabric_internal.h | 19 +
drivers/gpu/drm/fabric/drm_fabric_netlink.c | 1035 ++++++++++++++++++
drivers/gpu/drm/fabric/drm_fabric_nl.c | 125 +++
7 files changed, 1345 insertions(+), 19 deletions(-)
create mode 100644 drivers/gpu/drm/fabric/drm_fabric_netlink.c
create mode 100644 drivers/gpu/drm/fabric/drm_fabric_nl.c
diff --git a/Documentation/gpu/drm-fabric.rst b/Documentation/gpu/drm-fabric.rst
index d2c9e694b67f..a8b33d6cd618 100644
--- a/Documentation/gpu/drm-fabric.rst
+++ b/Documentation/gpu/drm-fabric.rst
@@ -18,8 +18,8 @@ Key Goals:
(xGMI, UALink and similar), enabling data-center discovery and monitoring.
* Support read-only enumeration, monitoring and state queries for
provider-owned topology.
-* Offer a flexible, future-proof interface that can be extended with new fabric
- types and attributes without breaking the uAPI.
+* Allow new attributes and fabric types to be added without reusing existing
+ wire identifiers, so the uAPI extends without breaking existing consumers.
* Allow multiple endpoints and ports per provider, so drivers can model
accelerator attachments, links and their peers.
@@ -84,10 +84,14 @@ an accelerator on another node, or a local endpoint that merely unregistered
-- so only the provider knows when a port's physical adjacency actually
changed, and only the provider retracts or replaces the descriptor.
-Endpoint teardown removes the endpoint's owned half-edges without generating
-a separate event for each port: the delete already describes the transition,
-so removing an endpoint advances the topology generation once rather than
-once per child port.
+``port-peer-delete-ntf`` reports an explicitly retracted half-edge; it is
+not emitted when a peer merely becomes locally unresolvable, so its absence
+is not evidence the far end is still reachable.
+
+Endpoint teardown removes the endpoint's owned half-edges without
+generating a separate event for each port: the delete already describes
+the transition, so removing an endpoint advances the topology generation
+once rather than once per child port.
Driver API
----------
@@ -175,3 +179,135 @@ objects and are not persistent hardware identities: they remain valid for the
lifetime of the registered object, but may disappear or be reused after the
object is unregistered. ``instance-id`` and ``fabric-ep-id`` carry
provider-defined identity, whose scope is described by the containing object.
+
+Query operations
+================
+
+User space enumerates topology with four read-only commands, each supporting a
+single lookup (``do``) and a bulk dump (``dump``):
+
+* ``fabric-get`` -- enumerate fabrics (``do`` by ``fabric-id``, ``dump`` for all).
+* ``endpoint-get`` -- enumerate endpoints, optionally filtered by ``fabric-id``,
+ or resolve one by ``endpoint-id`` or backing ``dev-name``/``bus-name``.
+* ``port-get`` -- enumerate ports, filtered by ``endpoint-id``. Ports may report
+ the provider's maximum capability as ``max-lane-count`` and
+ ``max-lane-signaling-rate-mbps`` (zero means unknown); these are maxima, not
+ the currently negotiated width or rate.
+* ``port-stats-get`` -- per-port statistics, filtered by ``endpoint-id``. A
+ targeted request for a port whose provider does not implement
+ ``port_stats_get`` returns ``-EOPNOTSUPP``. During a dump, ports without
+ statistics support are omitted and enumeration continues with later ports;
+ any other provider error ends the dump.
+
+Notifications
+-------------
+
+Subscribe to the ``monitor`` multicast group to receive asynchronous change
+notifications. Full-object notifications reuse the shape of their matching
+``get`` reply and are declared with ``notify:``; peer-link notifications carry
+a partial payload and are declared with ``event:``.
+
+.. list-table::
+ :header-rows: 1
+
+ * - Notification
+ - Trigger
+ - Payload
+ * - ``fabric-create-ntf``
+ - a fabric is registered by a provider
+ - reuses ``fabric-get``
+ * - ``fabric-delete-ntf``
+ - a provider-owned fabric is unregistered
+ - reuses ``fabric-get``
+ * - ``endpoint-create-ntf``
+ - an endpoint is registered
+ - reuses ``endpoint-get``
+ * - ``endpoint-delete-ntf``
+ - an endpoint is unregistered
+ - reuses ``endpoint-get``
+ * - ``port-change-ntf``
+ - a port's operational state changes
+ - reuses ``port-get``
+ * - ``port-peer-create-ntf``
+ - a port's peer descriptor is set by its provider
+ - partial (``event:``)
+ * - ``port-peer-delete-ntf``
+ - a port's peer descriptor is explicitly unset by its provider; never
+ emitted for an implicit half-edge loss (see `Peer semantics`_)
+ - partial (``event:``)
+
+Notifications are best-effort. A listener that detects loss, restarts, or
+receives an interrupted dump must rebuild state with the query commands.
+
+Topology generation and dump consistency
+----------------------------------------
+
+The core keeps a nonzero generation counter and advances it whenever topology or
+exposed state changes. It is surfaced as the ``topology-generation`` attribute on
+``fabric-get``, ``endpoint-get`` and ``port-get`` replies and on the topology
+notifications. A provider-reported change advances the generation before its
+notification is serialized, so an event carries the post-change value that a
+later ``get``/``dump`` will also report.
+
+``topology-generation`` is a change token, not a timestamp, liveness counter or
+event count: a changed value means topology changed, but the delta between two
+values has no defined meaning and the counter may wrap (it skips zero). Statistics
+reads do not advance it.
+
+The same value backs dump consistency. Every multipart dump samples it and calls
+``genl_dump_check_consistent()``; if it changes between dump batches, Generic
+Netlink marks the dump with ``NLM_F_DUMP_INTR``, meaning the snapshot may be torn.
+User space must then discard the partial result and retry the complete dump. The
+generation does not apply to statistics reads.
+
+Only a dump that serialized at least one entry can carry ``NLM_F_DUMP_INTR``,
+because Generic Netlink arms the consistency check on the first entry it emits.
+A dump that yields no entries at all cannot report interruption, so user space
+should treat an empty result as advisory and re-read ``topology-generation``
+before concluding that the topology is empty.
+
+Namespaces
+----------
+
+DRM Fabric objects describe host-global hardware and are not scoped per network
+namespace. Query and dump operations (``*-get``) are therefore accepted only from
+the initial network namespace; a request from any other network namespace fails
+with ``-EPERM``. The Generic Netlink family is registered ``netnsok`` (so it
+resolves in any network namespace and can return that policy error rather than
+being invisible), but the operations themselves remain confined to ``init_net``.
+Monitor notifications are likewise emitted only into ``init_net``, so a listener
+that joins the multicast group from another network namespace never receives
+them.
+
+Examples
+========
+
+Query the topology with the in-tree YNL tool, pointing it at the spec:
+
+.. code-block:: bash
+
+ # List all fabrics
+ ./tools/net/ynl/pyynl/cli.py \
+ --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.
+
+List the endpoints of a fabric:
+
+.. code-block:: bash
+
+ ./tools/net/ynl/pyynl/cli.py \
+ --spec Documentation/netlink/specs/drm_fabric.yaml \
+ --dump endpoint-get --json '{"fabric-id": 1}'
+
+Query a single port:
+
+.. code-block:: bash
+
+ ./tools/net/ynl/pyynl/cli.py \
+ --spec Documentation/netlink/specs/drm_fabric.yaml \
+ --do port-get --json '{"endpoint-id": 1, "port-index": 0}'
+
+The family name on the wire is ``drm-fabric``.
diff --git a/drivers/gpu/drm/fabric/Kconfig b/drivers/gpu/drm/fabric/Kconfig
index 21fbfae9863d..7e9c569fd258 100644
--- a/drivers/gpu/drm/fabric/Kconfig
+++ b/drivers/gpu/drm/fabric/Kconfig
@@ -4,9 +4,9 @@ config DRM_FABRIC
tristate "DRM fabric support"
depends on DRM && NET
help
- Enable DRM fabric support. This infrastructure provides the
- core object model and provider API for registered accelerator
- interconnect topologies.
+ Enable DRM fabric support. This infrastructure exposes
+ registered accelerator interconnect topologies to userspace
+ through the drm-fabric generic netlink family.
To compile this as a module, choose M here: the module will be
called drm-fabric.
diff --git a/drivers/gpu/drm/fabric/Makefile b/drivers/gpu/drm/fabric/Makefile
index 3a76f31f1e83..cf9d9d6be3d3 100644
--- a/drivers/gpu/drm/fabric/Makefile
+++ b/drivers/gpu/drm/fabric/Makefile
@@ -1,4 +1,4 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_DRM_FABRIC) += drm-fabric.o
-drm-fabric-y := drm_fabric.o
+drm-fabric-y := drm_fabric.o drm_fabric_netlink.o drm_fabric_nl.o
diff --git a/drivers/gpu/drm/fabric/drm_fabric.c b/drivers/gpu/drm/fabric/drm_fabric.c
index 8769d7bdcde1..ce331656af70 100644
--- a/drivers/gpu/drm/fabric/drm_fabric.c
+++ b/drivers/gpu/drm/fabric/drm_fabric.c
@@ -186,7 +186,7 @@ struct drm_fabric *drm_fabric_register(const struct drm_fabric_desc *desc)
xa_limit_32b, GFP_KERNEL);
if (ret)
return ERR_PTR(ret);
- drm_fabric_base_seq_inc();
+ drm_fabric_emit_fabric_create(fabric, drm_fabric_base_seq_inc());
}
return_ptr(fabric);
@@ -270,7 +270,9 @@ int drm_fabric_unregister(struct drm_fabric *fabric)
*/
if (WARN_ON_ONCE(drm_fabric_has_members(fabric)))
return -EBUSY;
- drm_fabric_base_seq_inc();
+ /* Emit before the erase, while @fabric is still live. */
+ drm_fabric_emit_fabric_delete(fabric,
+ drm_fabric_base_seq_inc());
xa_erase(&drm_fabric_xa, fabric->id);
}
@@ -410,7 +412,7 @@ drm_fabric_endpoint_register(struct drm_fabric *fabric,
break;
drm_fabric_get(fabric);
- drm_fabric_base_seq_inc();
+ drm_fabric_emit_endpoint_create(ep, drm_fabric_base_seq_inc());
}
if (ret) {
@@ -499,7 +501,8 @@ void drm_fabric_endpoint_unregister(struct drm_fabric_endpoint *ep)
scoped_guard(mutex, &drm_fabric_lock) {
if (WARN_ON_ONCE(!drm_fabric_ep_is_registered(ep)))
return;
- drm_fabric_base_seq_inc();
+ /* Emit before the erase, while @ep is still live. */
+ drm_fabric_emit_endpoint_delete(ep, drm_fabric_base_seq_inc());
xa_erase(&drm_fabric_ep_xa, ep->id);
}
@@ -587,7 +590,8 @@ int drm_fabric_port_set_peer(struct drm_fabric_port *port,
return -EEXIST;
port->peer = *peer;
port->has_peer = true;
- drm_fabric_base_seq_inc();
+ drm_fabric_emit_port_peer_create(port, peer,
+ drm_fabric_base_seq_inc());
}
return 0;
@@ -612,7 +616,9 @@ int drm_fabric_port_unset_peer(struct drm_fabric_port *port)
scoped_guard(mutex, &drm_fabric_lock) {
if (!port->has_peer)
return -ENOENT;
- drm_fabric_base_seq_inc();
+ /* Emit before clearing to show the peer being removed. */
+ drm_fabric_emit_port_peer_delete(port, &port->peer,
+ drm_fabric_base_seq_inc());
port->has_peer = false;
memset(&port->peer, 0, sizeof(port->peer));
}
@@ -654,19 +660,24 @@ void drm_fabric_port_set_oper(struct drm_fabric_port *port,
enum drm_fabric_port_state old = port->oper_state;
port->oper_state = state;
- if (old != state)
- drm_fabric_base_seq_inc();
+ if (old != state) {
+ u32 gen = drm_fabric_base_seq_inc();
+
+ drm_fabric_emit_port_change(port, gen);
+ }
}
}
EXPORT_SYMBOL(drm_fabric_port_set_oper);
static int __init drm_fabric_init(void)
{
- return 0;
+ return drm_fabric_netlink_register();
}
static void __exit drm_fabric_exit(void)
{
+ drm_fabric_netlink_unregister();
+
WARN_ON(!xa_empty(&drm_fabric_xa));
WARN_ON(!xa_empty(&drm_fabric_ep_xa));
xa_destroy(&drm_fabric_xa);
diff --git a/drivers/gpu/drm/fabric/drm_fabric_internal.h b/drivers/gpu/drm/fabric/drm_fabric_internal.h
index d454225a0b81..7fc5bc3f33f9 100644
--- a/drivers/gpu/drm/fabric/drm_fabric_internal.h
+++ b/drivers/gpu/drm/fabric/drm_fabric_internal.h
@@ -18,6 +18,9 @@ extern struct xarray drm_fabric_ep_xa; /* Endpoint registry */
extern u32 drm_fabric_base_seq; /* Dump consistency sequence */
+int drm_fabric_netlink_register(void);
+void drm_fabric_netlink_unregister(void);
+
struct drm_fabric *drm_fabric_find_by_id(u32 id);
struct drm_fabric_endpoint *drm_fabric_endpoint_find_by_id(u32 id);
struct drm_fabric_endpoint *
@@ -31,4 +34,20 @@ void drm_fabric_endpoint_put(struct drm_fabric_endpoint *ep);
struct drm_fabric *drm_fabric_get(struct drm_fabric *fabric);
void drm_fabric_put(struct drm_fabric *fabric);
+/* Emits use the post-change generation. */
+void drm_fabric_emit_endpoint_create(struct drm_fabric_endpoint *ep, u32 generation);
+void drm_fabric_emit_endpoint_delete(struct drm_fabric_endpoint *ep, u32 generation);
+
+void drm_fabric_emit_port_peer_create(struct drm_fabric_port *port,
+ const struct drm_fabric_peer *peer,
+ u32 generation);
+void drm_fabric_emit_port_peer_delete(struct drm_fabric_port *port,
+ const struct drm_fabric_peer *peer,
+ u32 generation);
+
+void drm_fabric_emit_port_change(struct drm_fabric_port *port, u32 generation);
+
+void drm_fabric_emit_fabric_create(struct drm_fabric *fabric, u32 generation);
+void drm_fabric_emit_fabric_delete(struct drm_fabric *fabric, u32 generation);
+
#endif /* __DRM_FABRIC_INTERNAL_H__ */
diff --git a/drivers/gpu/drm/fabric/drm_fabric_netlink.c b/drivers/gpu/drm/fabric/drm_fabric_netlink.c
new file mode 100644
index 000000000000..fef2d4c8f5bb
--- /dev/null
+++ b/drivers/gpu/drm/fabric/drm_fabric_netlink.c
@@ -0,0 +1,1035 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright © 2026 Intel Corporation
+ */
+
+#include <linux/cleanup.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <net/genetlink.h>
+#include <net/net_namespace.h>
+
+#include <drm/drm_fabric.h>
+#include <uapi/drm/drm_fabric.h>
+
+#include "drm_fabric_internal.h"
+#include "drm_fabric_nl.h"
+
+struct drm_fabric_dump_ctx {
+ unsigned long idx;
+};
+
+static struct drm_fabric_dump_ctx *
+drm_fabric_dump_context(struct netlink_callback *cb)
+{
+ return (struct drm_fabric_dump_ctx *)cb->ctx;
+}
+
+/* Resume cursor for the nested per-port dumps (PORT_GET, PORT_STATS_GET). */
+struct drm_fabric_port_dump_ctx {
+ unsigned long ep_idx;
+ unsigned long port_idx;
+};
+
+static struct drm_fabric_port_dump_ctx *
+drm_fabric_port_dump_context(struct netlink_callback *cb)
+{
+ return (struct drm_fabric_port_dump_ctx *)cb->ctx;
+}
+
+/* netnsok so a non-init_net caller gets -EPERM, not a missing family. */
+static int drm_fabric_nl_host_only(const struct net *net)
+{
+ return net_eq(net, &init_net) ? 0 : -EPERM;
+}
+
+static int drm_fabric_fill_fabric(struct sk_buff *skb,
+ struct drm_fabric *fabric)
+{
+ struct nlattr *nest;
+
+ nest = nla_nest_start(skb, DRM_FABRIC_A_FABRIC);
+ if (!nest)
+ return -EMSGSIZE;
+
+ if (nla_put_u32(skb, DRM_FABRIC_A_FABRIC_ATTRS_FABRIC_ID, fabric->id) ||
+ nla_put_u32(skb, DRM_FABRIC_A_FABRIC_ATTRS_TYPE, fabric->type) ||
+ nla_put_string(skb, DRM_FABRIC_A_FABRIC_ATTRS_NAME, fabric->name) ||
+ nla_put_u64_64bit(skb, DRM_FABRIC_A_FABRIC_ATTRS_INSTANCE_ID,
+ fabric->instance_id, DRM_FABRIC_A_FABRIC_ATTRS_PAD)) {
+ nla_nest_cancel(skb, nest);
+ return -EMSGSIZE;
+ }
+
+ nla_nest_end(skb, nest);
+ return 0;
+}
+
+static int drm_fabric_fill_endpoint(struct sk_buff *skb,
+ struct drm_fabric_endpoint *ep)
+{
+ struct nlattr *nest;
+
+ nest = nla_nest_start(skb, DRM_FABRIC_A_ENDPOINT);
+ if (!nest)
+ return -EMSGSIZE;
+
+ if (nla_put_u32(skb, DRM_FABRIC_A_ENDPOINT_ATTRS_ENDPOINT_ID, ep->id) ||
+ nla_put_u32(skb, DRM_FABRIC_A_ENDPOINT_ATTRS_FABRIC_ID,
+ drm_fabric_endpoint_fabric_id(ep)) ||
+ nla_put_u64_64bit(skb, DRM_FABRIC_A_ENDPOINT_ATTRS_FABRIC_EP_ID,
+ ep->fabric_ep_id, DRM_FABRIC_A_ENDPOINT_ATTRS_PAD) ||
+ nla_put_string(skb, DRM_FABRIC_A_ENDPOINT_ATTRS_NAME, ep->name) ||
+ nla_put_string(skb, DRM_FABRIC_A_ENDPOINT_ATTRS_DEV_NAME,
+ dev_name(ep->parent)) ||
+ nla_put_string(skb, DRM_FABRIC_A_ENDPOINT_ATTRS_BUS_NAME,
+ dev_bus_name(ep->parent))) {
+ nla_nest_cancel(skb, nest);
+ return -EMSGSIZE;
+ }
+
+ nla_nest_end(skb, nest);
+ return 0;
+}
+
+static int drm_fabric_fill_peer(struct sk_buff *skb,
+ struct drm_fabric_port *port)
+{
+ struct nlattr *nest;
+
+ if (!port->has_peer)
+ return 0;
+
+ nest = nla_nest_start(skb, DRM_FABRIC_A_PORT_ATTRS_PEER);
+ if (!nest)
+ return -EMSGSIZE;
+
+ if (nla_put_u64_64bit(skb, DRM_FABRIC_A_PEER_ATTRS_PEER_ID,
+ port->peer.peer_id, DRM_FABRIC_A_PEER_ATTRS_PAD) ||
+ nla_put_u32(skb, DRM_FABRIC_A_PEER_ATTRS_TYPE,
+ port->peer.peer_type) ||
+ nla_put_u32(skb, DRM_FABRIC_A_PEER_ATTRS_PORT_INDEX,
+ port->peer.port_index)) {
+ nla_nest_cancel(skb, nest);
+ return -EMSGSIZE;
+ }
+
+ nla_nest_end(skb, nest);
+ return 0;
+}
+
+static int drm_fabric_fill_port(struct sk_buff *skb,
+ struct drm_fabric_port *port)
+{
+ struct nlattr *nest;
+ int ret;
+
+ nest = nla_nest_start(skb, DRM_FABRIC_A_PORT);
+ if (!nest)
+ return -EMSGSIZE;
+
+ if (nla_put_u32(skb, DRM_FABRIC_A_PORT_ATTRS_PORT_INDEX, port->index) ||
+ nla_put_u32(skb, DRM_FABRIC_A_PORT_ATTRS_ENDPOINT_ID, port->endpoint->id) ||
+ nla_put_u32(skb, DRM_FABRIC_A_PORT_ATTRS_OPER_STATE,
+ port->oper_state) ||
+ nla_put_u32(skb, DRM_FABRIC_A_PORT_ATTRS_MAX_LANE_COUNT,
+ port->max_lane_count) ||
+ nla_put_u32(skb, DRM_FABRIC_A_PORT_ATTRS_MAX_LANE_SIGNALING_RATE_MBPS,
+ port->max_lane_signaling_rate_mbps)) {
+ nla_nest_cancel(skb, nest);
+ return -EMSGSIZE;
+ }
+
+ ret = drm_fabric_fill_peer(skb, port);
+ if (ret) {
+ nla_nest_cancel(skb, nest);
+ return ret;
+ }
+
+ nla_nest_end(skb, nest);
+ return 0;
+}
+
+static int drm_fabric_fill_port_stats(struct sk_buff *skb,
+ struct drm_fabric_port *port,
+ struct drm_fabric_port_stats *stats)
+{
+ struct nlattr *nest;
+
+ nest = nla_nest_start(skb, DRM_FABRIC_A_PORT_STATS);
+ if (!nest)
+ return -EMSGSIZE;
+
+ if (nla_put_u32(skb, DRM_FABRIC_A_PORT_STATS_ATTRS_ENDPOINT_ID,
+ port->endpoint->id) ||
+ nla_put_u32(skb, DRM_FABRIC_A_PORT_STATS_ATTRS_PORT_INDEX, port->index) ||
+ nla_put_u64_64bit(skb, DRM_FABRIC_A_PORT_STATS_ATTRS_READ_BYTES,
+ stats->read_bytes, DRM_FABRIC_A_PORT_STATS_ATTRS_PAD) ||
+ nla_put_u64_64bit(skb, DRM_FABRIC_A_PORT_STATS_ATTRS_WRITE_BYTES,
+ stats->write_bytes, DRM_FABRIC_A_PORT_STATS_ATTRS_PAD) ||
+ nla_put_u64_64bit(skb, DRM_FABRIC_A_PORT_STATS_ATTRS_LINK_DOWN_COUNT,
+ stats->link_down_count, DRM_FABRIC_A_PORT_STATS_ATTRS_PAD) ||
+ nla_put_u64_64bit(skb, DRM_FABRIC_A_PORT_STATS_ATTRS_RETRAIN_COUNT,
+ stats->retrain_count, DRM_FABRIC_A_PORT_STATS_ATTRS_PAD)) {
+ nla_nest_cancel(skb, nest);
+ return -EMSGSIZE;
+ }
+
+ nla_nest_end(skb, nest);
+ return 0;
+}
+
+int drm_fabric_nl_fabric_get_doit(struct sk_buff *skb, struct genl_info *info)
+{
+ struct drm_fabric *fabric;
+ struct sk_buff *msg;
+ u32 fabric_id;
+ void *hdr;
+ int ret;
+
+ ret = drm_fabric_nl_host_only(genl_info_net(info));
+ if (ret)
+ return ret;
+
+ if (GENL_REQ_ATTR_CHECK(info, DRM_FABRIC_A_FABRIC_ID))
+ return -EINVAL;
+
+ fabric_id = nla_get_u32(info->attrs[DRM_FABRIC_A_FABRIC_ID]);
+
+ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+
+ hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq,
+ &drm_fabric_nl_family, 0,
+ DRM_FABRIC_CMD_FABRIC_GET);
+ if (!hdr) {
+ nlmsg_free(msg);
+ return -EMSGSIZE;
+ }
+
+ scoped_guard(mutex, &drm_fabric_lock) {
+ fabric = drm_fabric_find_by_id(fabric_id);
+ if (!fabric) {
+ nlmsg_free(msg);
+ return -ENOENT;
+ }
+
+ ret = drm_fabric_fill_fabric(msg, fabric);
+ if (ret) {
+ nlmsg_free(msg);
+ return ret;
+ }
+
+ if (nla_put_u32(msg, DRM_FABRIC_A_TOPOLOGY_GENERATION,
+ drm_fabric_base_seq)) {
+ nlmsg_free(msg);
+ return -EMSGSIZE;
+ }
+ }
+
+ genlmsg_end(msg, hdr);
+ return genlmsg_reply(msg, info);
+}
+
+int drm_fabric_nl_fabric_get_dumpit(struct sk_buff *skb,
+ struct netlink_callback *cb)
+{
+ struct drm_fabric_dump_ctx *ctx = drm_fabric_dump_context(cb);
+ struct drm_fabric *fabric;
+ unsigned long fidx;
+ void *hdr;
+ int ret;
+
+ ret = drm_fabric_nl_host_only(genl_info_net(genl_info_dump(cb)));
+ if (ret)
+ return ret;
+
+ scoped_guard(mutex, &drm_fabric_lock) {
+ cb->seq = drm_fabric_base_seq;
+ xa_for_each_start(&drm_fabric_xa, fidx, fabric, ctx->idx) {
+ hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid,
+ cb->nlh->nlmsg_seq,
+ &drm_fabric_nl_family, NLM_F_MULTI,
+ DRM_FABRIC_CMD_FABRIC_GET);
+ if (!hdr) {
+ ret = -EMSGSIZE;
+ break;
+ }
+ genl_dump_check_consistent(cb, hdr);
+
+ if (nla_put_u32(skb, DRM_FABRIC_A_TOPOLOGY_GENERATION,
+ drm_fabric_base_seq)) {
+ genlmsg_cancel(skb, hdr);
+ ret = -EMSGSIZE;
+ break;
+ }
+
+ ret = drm_fabric_fill_fabric(skb, fabric);
+ if (ret) {
+ genlmsg_cancel(skb, hdr);
+ break;
+ }
+
+ genlmsg_end(skb, hdr);
+ }
+ }
+
+ if (ret == -EMSGSIZE) {
+ ctx->idx = fidx;
+ return skb->len;
+ }
+ return ret;
+}
+
+/* Identified by core-assigned id, or by bus + dev name. */
+static struct drm_fabric_endpoint *
+drm_fabric_resolve_endpoint(struct genl_info *info)
+{
+ struct nlattr **attrs = info->attrs;
+ const char *devname = NULL;
+ const char *busname = NULL;
+ struct drm_fabric_endpoint *ep;
+
+ lockdep_assert_held(&drm_fabric_lock);
+
+ if (attrs[DRM_FABRIC_A_DEV_NAME])
+ devname = nla_data(attrs[DRM_FABRIC_A_DEV_NAME]);
+ if (attrs[DRM_FABRIC_A_BUS_NAME])
+ busname = nla_data(attrs[DRM_FABRIC_A_BUS_NAME]);
+
+ if (attrs[DRM_FABRIC_A_ENDPOINT_ID]) {
+ u32 ep_id = nla_get_u32(attrs[DRM_FABRIC_A_ENDPOINT_ID]);
+
+ ep = drm_fabric_endpoint_find_by_id(ep_id);
+ if (!ep)
+ return ERR_PTR(-ENOENT);
+
+ if (devname && strcmp(dev_name(ep->parent), devname))
+ return ERR_PTR(-EINVAL);
+ if (busname && strcmp(dev_bus_name(ep->parent), busname))
+ return ERR_PTR(-EINVAL);
+
+ return ep;
+ }
+
+ if (!devname)
+ return ERR_PTR(-EINVAL);
+
+ ep = drm_fabric_endpoint_find_by_dev_name(devname, busname);
+ if (IS_ERR(ep))
+ return ep;
+ if (!ep)
+ return ERR_PTR(-ENOENT);
+
+ return ep;
+}
+
+int drm_fabric_nl_endpoint_get_doit(struct sk_buff *skb,
+ struct genl_info *info)
+{
+ struct drm_fabric_endpoint *ep;
+ struct sk_buff *msg;
+ void *hdr;
+ int ret;
+
+ ret = drm_fabric_nl_host_only(genl_info_net(info));
+ if (ret)
+ return ret;
+
+ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+
+ hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq,
+ &drm_fabric_nl_family, 0,
+ DRM_FABRIC_CMD_ENDPOINT_GET);
+ if (!hdr) {
+ nlmsg_free(msg);
+ return -EMSGSIZE;
+ }
+
+ scoped_guard(mutex, &drm_fabric_lock) {
+ ep = drm_fabric_resolve_endpoint(info);
+ if (IS_ERR(ep)) {
+ nlmsg_free(msg);
+ return PTR_ERR(ep);
+ }
+
+ ret = drm_fabric_fill_endpoint(msg, ep);
+ if (ret) {
+ nlmsg_free(msg);
+ return ret;
+ }
+
+ if (nla_put_u32(msg, DRM_FABRIC_A_TOPOLOGY_GENERATION,
+ drm_fabric_base_seq)) {
+ nlmsg_free(msg);
+ return -EMSGSIZE;
+ }
+ }
+
+ genlmsg_end(msg, hdr);
+ return genlmsg_reply(msg, info);
+}
+
+int drm_fabric_nl_endpoint_get_dumpit(struct sk_buff *skb,
+ struct netlink_callback *cb)
+{
+ const struct genl_info *info = genl_info_dump(cb);
+ struct drm_fabric_dump_ctx *ctx = drm_fabric_dump_context(cb);
+ u32 filter_fabric_id = 0;
+ bool has_filter = false;
+ struct drm_fabric_endpoint *ep;
+ unsigned long eidx;
+ void *hdr;
+ int ret;
+
+ ret = drm_fabric_nl_host_only(genl_info_net(info));
+ if (ret)
+ return ret;
+
+ if (info->attrs[DRM_FABRIC_A_FABRIC_ID]) {
+ filter_fabric_id =
+ nla_get_u32(info->attrs[DRM_FABRIC_A_FABRIC_ID]);
+ has_filter = true;
+ }
+
+ scoped_guard(mutex, &drm_fabric_lock) {
+ cb->seq = drm_fabric_base_seq;
+ xa_for_each_start(&drm_fabric_ep_xa, eidx, ep, ctx->idx) {
+ if (has_filter &&
+ drm_fabric_endpoint_fabric_id(ep) != filter_fabric_id)
+ continue;
+
+ hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid,
+ cb->nlh->nlmsg_seq,
+ &drm_fabric_nl_family, NLM_F_MULTI,
+ DRM_FABRIC_CMD_ENDPOINT_GET);
+ if (!hdr) {
+ ret = -EMSGSIZE;
+ break;
+ }
+ genl_dump_check_consistent(cb, hdr);
+
+ if (nla_put_u32(skb, DRM_FABRIC_A_TOPOLOGY_GENERATION,
+ drm_fabric_base_seq)) {
+ genlmsg_cancel(skb, hdr);
+ ret = -EMSGSIZE;
+ break;
+ }
+
+ ret = drm_fabric_fill_endpoint(skb, ep);
+ if (ret) {
+ genlmsg_cancel(skb, hdr);
+ break;
+ }
+
+ genlmsg_end(skb, hdr);
+ }
+ }
+
+ if (ret == -EMSGSIZE) {
+ ctx->idx = eidx;
+ return skb->len;
+ }
+ return ret;
+}
+
+static int drm_fabric_port_key(struct genl_info *info, u32 *ep_id, u32 *port_idx)
+{
+ if (GENL_REQ_ATTR_CHECK(info, DRM_FABRIC_A_ENDPOINT_ID) ||
+ GENL_REQ_ATTR_CHECK(info, DRM_FABRIC_A_PORT_INDEX))
+ return -EINVAL;
+
+ *ep_id = nla_get_u32(info->attrs[DRM_FABRIC_A_ENDPOINT_ID]);
+ *port_idx = nla_get_u32(info->attrs[DRM_FABRIC_A_PORT_INDEX]);
+ return 0;
+}
+
+int drm_fabric_nl_port_get_doit(struct sk_buff *skb,
+ struct genl_info *info)
+{
+ struct drm_fabric_port *port;
+ struct sk_buff *msg;
+ u32 ep_id, port_idx;
+ void *hdr;
+ int ret;
+
+ ret = drm_fabric_nl_host_only(genl_info_net(info));
+ if (ret)
+ return ret;
+
+ ret = drm_fabric_port_key(info, &ep_id, &port_idx);
+ if (ret)
+ return ret;
+
+ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+
+ hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq,
+ &drm_fabric_nl_family, 0,
+ DRM_FABRIC_CMD_PORT_GET);
+ if (!hdr) {
+ nlmsg_free(msg);
+ return -EMSGSIZE;
+ }
+
+ scoped_guard(mutex, &drm_fabric_lock) {
+ port = drm_fabric_port_find(ep_id, port_idx);
+ if (!port) {
+ nlmsg_free(msg);
+ return -ENOENT;
+ }
+
+ ret = drm_fabric_fill_port(msg, port);
+ if (ret) {
+ nlmsg_free(msg);
+ return ret;
+ }
+
+ if (nla_put_u32(msg, DRM_FABRIC_A_TOPOLOGY_GENERATION,
+ drm_fabric_base_seq)) {
+ nlmsg_free(msg);
+ return -EMSGSIZE;
+ }
+ }
+
+ genlmsg_end(msg, hdr);
+ return genlmsg_reply(msg, info);
+}
+
+static int
+drm_fabric_dump_endpoint_ports(struct sk_buff *skb,
+ struct netlink_callback *cb,
+ struct drm_fabric_endpoint *ep, u8 cmd,
+ int (*fill)(struct sk_buff *,
+ struct drm_fabric_port *),
+ unsigned long *s_port_idx)
+{
+ struct drm_fabric_port *port;
+ unsigned long pidx;
+ void *hdr;
+ int ret = 0;
+
+ lockdep_assert_held(&drm_fabric_lock);
+
+ xa_for_each_start(&ep->ports, pidx, port, *s_port_idx) {
+ hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid,
+ cb->nlh->nlmsg_seq, &drm_fabric_nl_family,
+ NLM_F_MULTI, cmd);
+ if (!hdr) {
+ ret = -EMSGSIZE;
+ break;
+ }
+ genl_dump_check_consistent(cb, hdr);
+
+ /* PORT_STATS_GET replies carry no topology-generation. */
+ if (cmd == DRM_FABRIC_CMD_PORT_GET &&
+ nla_put_u32(skb, DRM_FABRIC_A_TOPOLOGY_GENERATION,
+ drm_fabric_base_seq)) {
+ genlmsg_cancel(skb, hdr);
+ ret = -EMSGSIZE;
+ break;
+ }
+
+ ret = fill(skb, port);
+ if (ret) {
+ genlmsg_cancel(skb, hdr);
+ break;
+ }
+
+ genlmsg_end(skb, hdr);
+ }
+
+ /* Only a partially emitted endpoint keeps its port cursor. */
+ *s_port_idx = (ret == -EMSGSIZE) ? pidx : 0;
+ return ret;
+}
+
+static int drm_fabric_port_dump(struct sk_buff *skb,
+ struct netlink_callback *cb,
+ int cmd,
+ int (*fill)(struct sk_buff *, struct drm_fabric_port *))
+{
+ const struct genl_info *info = genl_info_dump(cb);
+ struct drm_fabric_port_dump_ctx *ctx = drm_fabric_port_dump_context(cb);
+ u32 filter_ep_id = 0;
+ bool has_ep_filter = false;
+ struct drm_fabric_endpoint *ep;
+ unsigned long eidx;
+ int ret;
+
+ ret = drm_fabric_nl_host_only(genl_info_net(info));
+ if (ret)
+ return ret;
+
+ if (info->attrs[DRM_FABRIC_A_ENDPOINT_ID]) {
+ filter_ep_id = nla_get_u32(info->attrs[DRM_FABRIC_A_ENDPOINT_ID]);
+ has_ep_filter = true;
+ }
+
+ scoped_guard(mutex, &drm_fabric_lock) {
+ bool first = true;
+
+ cb->seq = drm_fabric_base_seq;
+ xa_for_each_start(&drm_fabric_ep_xa, eidx, ep, ctx->ep_idx) {
+ /*
+ * port_idx belongs to ep_idx; honor it only if
+ * that endpoint still resolves.
+ */
+ if (first) {
+ if (eidx != ctx->ep_idx)
+ ctx->port_idx = 0;
+ first = false;
+ }
+
+ if (has_ep_filter && ep->id != filter_ep_id) {
+ ctx->port_idx = 0;
+ continue;
+ }
+
+ ret = drm_fabric_dump_endpoint_ports(skb, cb, ep,
+ cmd,
+ fill,
+ &ctx->port_idx);
+ if (ret)
+ break;
+ }
+ }
+
+ if (ret == -EMSGSIZE) {
+ ctx->ep_idx = eidx;
+ return skb->len;
+ }
+ return ret;
+}
+
+int drm_fabric_nl_port_get_dumpit(struct sk_buff *skb,
+ struct netlink_callback *cb)
+{
+ return drm_fabric_port_dump(skb, cb, DRM_FABRIC_CMD_PORT_GET,
+ drm_fabric_fill_port);
+}
+
+/* Advance the cursor to the next port at or after it, or return NULL. */
+static struct drm_fabric_port *
+drm_fabric_stats_dump_next(unsigned long *ep_idx, unsigned long *port_idx,
+ bool has_ep_filter, u32 filter_ep_id)
+{
+ struct drm_fabric_endpoint *ep;
+ struct drm_fabric_port *port;
+ unsigned long eidx;
+ bool first = true;
+
+ lockdep_assert_held(&drm_fabric_lock);
+
+ xa_for_each_start(&drm_fabric_ep_xa, eidx, ep, *ep_idx) {
+ if (first) {
+ if (eidx != *ep_idx)
+ *port_idx = 0;
+ first = false;
+ }
+
+ if (has_ep_filter && ep->id != filter_ep_id) {
+ *port_idx = 0;
+ continue;
+ }
+
+ port = xa_find(&ep->ports, port_idx, ULONG_MAX, XA_PRESENT);
+ if (port) {
+ *ep_idx = eidx;
+ return port;
+ }
+
+ *port_idx = 0;
+ }
+
+ /* Mark the endpoint cursor exhausted. */
+ *ep_idx = ULONG_MAX;
+ return NULL;
+}
+
+/*
+ * Port indices are provider-chosen u32s, so port_index may be U32_MAX;
+ * adding one there would wrap to 0 and re-dump the endpoint forever.
+ */
+static void
+drm_fabric_stats_cursor_advance(unsigned long *ep_idx, unsigned long *port_idx,
+ u32 port_index)
+{
+ if (port_index == U32_MAX) {
+ (*ep_idx)++;
+ *port_idx = 0;
+ } else {
+ *port_idx = port_index + 1;
+ }
+}
+
+/* Advance past an unsupported port so a resumed dump cannot retry it. */
+static void
+drm_fabric_stats_dump_skip(struct sk_buff *skb, void *hdr,
+ struct drm_fabric_port_dump_ctx *ctx,
+ struct drm_fabric_port *port,
+ u32 port_index)
+{
+ genlmsg_cancel(skb, hdr);
+ drm_fabric_port_put(port);
+ drm_fabric_stats_cursor_advance(&ctx->ep_idx, &ctx->port_idx,
+ port_index);
+}
+
+int drm_fabric_nl_port_stats_get_doit(struct sk_buff *skb,
+ struct genl_info *info)
+{
+ const struct drm_fabric_ops *ops;
+ struct drm_fabric_port_stats stats = {};
+ struct drm_fabric_port *port;
+ struct sk_buff *msg;
+ u32 ep_id, port_idx;
+ void *hdr;
+ int ret;
+
+ ret = drm_fabric_nl_host_only(genl_info_net(info));
+ if (ret)
+ return ret;
+
+ ret = drm_fabric_port_key(info, &ep_id, &port_idx);
+ if (ret)
+ return ret;
+
+ /*
+ * Pins the port through its endpoint across the unlocked provider
+ * callback; endpoint_unregister() waits on this pin, so it cannot be
+ * freed underneath us.
+ */
+ port = drm_fabric_port_find_get(ep_id, port_idx);
+ if (IS_ERR(port))
+ return PTR_ERR(port);
+
+ ops = port->endpoint->ops;
+ if (!ops || !ops->port_stats_get) {
+ ret = -EOPNOTSUPP;
+ goto out_put;
+ }
+
+ lockdep_assert_not_held(&drm_fabric_lock);
+ ret = ops->port_stats_get(port, &stats);
+ if (ret)
+ goto out_put;
+
+ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!msg) {
+ ret = -ENOMEM;
+ goto out_put;
+ }
+
+ hdr = genlmsg_put(msg, info->snd_portid, info->snd_seq,
+ &drm_fabric_nl_family, 0,
+ DRM_FABRIC_CMD_PORT_STATS_GET);
+ if (!hdr) {
+ ret = -EMSGSIZE;
+ goto out_free;
+ }
+
+ ret = drm_fabric_fill_port_stats(msg, port, &stats);
+ if (ret)
+ goto out_free;
+
+ genlmsg_end(msg, hdr);
+ drm_fabric_port_put(port);
+ return genlmsg_reply(msg, info);
+
+out_free:
+ nlmsg_free(msg);
+out_put:
+ drm_fabric_port_put(port);
+ return ret;
+}
+
+/*
+ * Statistics callbacks may sleep. Pin the endpoint, drop the lock, then
+ * resample cb->seq so a mutation in that window sets NLM_F_DUMP_INTR.
+ */
+int drm_fabric_nl_port_stats_get_dumpit(struct sk_buff *skb,
+ struct netlink_callback *cb)
+{
+ const struct genl_info *info = genl_info_dump(cb);
+ struct drm_fabric_port_dump_ctx *ctx = drm_fabric_port_dump_context(cb);
+ u32 filter_ep_id = 0;
+ bool has_ep_filter = false;
+ int ret;
+
+ ret = drm_fabric_nl_host_only(genl_info_net(info));
+ if (ret)
+ return ret;
+
+ if (info->attrs[DRM_FABRIC_A_ENDPOINT_ID]) {
+ filter_ep_id = nla_get_u32(info->attrs[DRM_FABRIC_A_ENDPOINT_ID]);
+ has_ep_filter = true;
+ }
+
+ for (;;) {
+ const struct drm_fabric_ops *ops;
+ struct drm_fabric_port_stats stats = {};
+ struct drm_fabric_endpoint *ep;
+ struct drm_fabric_port *port;
+ u32 port_index;
+ void *hdr;
+
+ scoped_guard(mutex, &drm_fabric_lock) {
+ /*
+ * Sampled before the cursor check so even a batch
+ * that emits nothing can report a late mutation.
+ */
+ cb->seq = drm_fabric_base_seq;
+
+ port = drm_fabric_stats_dump_next(&ctx->ep_idx,
+ &ctx->port_idx,
+ has_ep_filter,
+ filter_ep_id);
+ /*
+ * Exhausted: 0 terminates the dump with NLMSG_DONE
+ * instead of re-running the parked cursor.
+ */
+ if (!port)
+ return 0;
+
+ ep = port->endpoint;
+ ops = ep->ops;
+ port_index = port->index;
+ drm_fabric_endpoint_get(ep);
+ }
+
+ /* Reserve the reply before invoking the sleeping provider callback. */
+ hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid,
+ cb->nlh->nlmsg_seq, &drm_fabric_nl_family,
+ NLM_F_MULTI, DRM_FABRIC_CMD_PORT_STATS_GET);
+ if (!hdr) {
+ drm_fabric_port_put(port);
+ return skb->len;
+ }
+
+ if (!ops || !ops->port_stats_get) {
+ drm_fabric_stats_dump_skip(skb, hdr, ctx, port, port_index);
+ continue;
+ }
+
+ lockdep_assert_not_held(&drm_fabric_lock);
+ ret = ops->port_stats_get(port, &stats);
+ if (ret == -EOPNOTSUPP) {
+ drm_fabric_stats_dump_skip(skb, hdr, ctx, port, port_index);
+ continue;
+ }
+ if (ret) {
+ genlmsg_cancel(skb, hdr);
+ drm_fabric_port_put(port);
+ break;
+ }
+
+ genl_dump_check_consistent(cb, hdr);
+
+ if (drm_fabric_fill_port_stats(skb, port, &stats)) {
+ genlmsg_cancel(skb, hdr);
+ drm_fabric_port_put(port);
+ return skb->len;
+ }
+
+ genlmsg_end(skb, hdr);
+ drm_fabric_port_put(port);
+
+ drm_fabric_stats_cursor_advance(&ctx->ep_idx, &ctx->port_idx,
+ port_index);
+ }
+
+ return ret;
+}
+
+void drm_fabric_emit_port_change(struct drm_fabric_port *port, u32 generation)
+{
+ struct sk_buff *msg;
+ void *hdr;
+
+ lockdep_assert_held(&drm_fabric_lock);
+
+ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!msg)
+ return;
+
+ hdr = genlmsg_put(msg, 0, 0, &drm_fabric_nl_family, 0, DRM_FABRIC_CMD_PORT_CHANGE_NTF);
+ if (!hdr) {
+ nlmsg_free(msg);
+ return;
+ }
+
+ if (nla_put_u32(msg, DRM_FABRIC_A_TOPOLOGY_GENERATION, generation) ||
+ drm_fabric_fill_port(msg, port)) {
+ nlmsg_free(msg);
+ return;
+ }
+
+ genlmsg_end(msg, hdr);
+ genlmsg_multicast(&drm_fabric_nl_family, msg, 0,
+ DRM_FABRIC_NLGRP_MONITOR, GFP_KERNEL);
+}
+
+static void drm_fabric_port_peer_event_send(enum drm_fabric_cmd cmd,
+ struct drm_fabric_port *port,
+ const struct drm_fabric_peer *peer,
+ u32 generation)
+{
+ struct sk_buff *msg;
+ void *hdr;
+ struct nlattr *nest;
+
+ lockdep_assert_held(&drm_fabric_lock);
+
+ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!msg)
+ return;
+
+ hdr = genlmsg_put(msg, 0, 0, &drm_fabric_nl_family, 0, cmd);
+ if (!hdr) {
+ nlmsg_free(msg);
+ return;
+ }
+
+ if (nla_put_u32(msg, DRM_FABRIC_A_TOPOLOGY_GENERATION, generation) ||
+ nla_put_u32(msg, DRM_FABRIC_A_ENDPOINT_ID, port->endpoint->id) ||
+ nla_put_u32(msg, DRM_FABRIC_A_PORT_INDEX, port->index)) {
+ nlmsg_free(msg);
+ return;
+ }
+
+ nest = nla_nest_start(msg, DRM_FABRIC_A_PEER);
+ if (!nest) {
+ nlmsg_free(msg);
+ return;
+ }
+
+ if (nla_put_u64_64bit(msg, DRM_FABRIC_A_PEER_ATTRS_PEER_ID,
+ peer->peer_id, DRM_FABRIC_A_PEER_ATTRS_PAD) ||
+ nla_put_u32(msg, DRM_FABRIC_A_PEER_ATTRS_TYPE, peer->peer_type) ||
+ nla_put_u32(msg, DRM_FABRIC_A_PEER_ATTRS_PORT_INDEX, peer->port_index)) {
+ nla_nest_cancel(msg, nest);
+ nlmsg_free(msg);
+ return;
+ }
+
+ nla_nest_end(msg, nest);
+
+ genlmsg_end(msg, hdr);
+ genlmsg_multicast(&drm_fabric_nl_family, msg, 0,
+ DRM_FABRIC_NLGRP_MONITOR, GFP_KERNEL);
+}
+
+void drm_fabric_emit_port_peer_create(struct drm_fabric_port *port,
+ const struct drm_fabric_peer *peer,
+ u32 generation)
+{
+ drm_fabric_port_peer_event_send(DRM_FABRIC_CMD_PORT_PEER_CREATE_NTF,
+ port, peer, generation);
+}
+
+void drm_fabric_emit_port_peer_delete(struct drm_fabric_port *port,
+ const struct drm_fabric_peer *peer,
+ u32 generation)
+{
+ drm_fabric_port_peer_event_send(DRM_FABRIC_CMD_PORT_PEER_DELETE_NTF,
+ port, peer, generation);
+}
+
+static void drm_fabric_endpoint_event_send(enum drm_fabric_cmd cmd,
+ struct drm_fabric_endpoint *ep,
+ u32 generation)
+{
+ struct sk_buff *msg;
+ void *hdr;
+
+ lockdep_assert_held(&drm_fabric_lock);
+
+ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!msg)
+ return;
+
+ hdr = genlmsg_put(msg, 0, 0, &drm_fabric_nl_family, 0, cmd);
+ if (!hdr) {
+ nlmsg_free(msg);
+ return;
+ }
+
+ if (nla_put_u32(msg, DRM_FABRIC_A_TOPOLOGY_GENERATION, generation) ||
+ drm_fabric_fill_endpoint(msg, ep)) {
+ nlmsg_free(msg);
+ return;
+ }
+
+ genlmsg_end(msg, hdr);
+ genlmsg_multicast(&drm_fabric_nl_family, msg, 0,
+ DRM_FABRIC_NLGRP_MONITOR, GFP_KERNEL);
+}
+
+void drm_fabric_emit_endpoint_create(struct drm_fabric_endpoint *ep, u32 generation)
+{
+ drm_fabric_endpoint_event_send(DRM_FABRIC_CMD_ENDPOINT_CREATE_NTF, ep,
+ generation);
+}
+
+void drm_fabric_emit_endpoint_delete(struct drm_fabric_endpoint *ep, u32 generation)
+{
+ drm_fabric_endpoint_event_send(DRM_FABRIC_CMD_ENDPOINT_DELETE_NTF, ep,
+ generation);
+}
+
+static void drm_fabric_fabric_event_send(enum drm_fabric_cmd cmd,
+ struct drm_fabric *fabric,
+ u32 generation)
+{
+ struct sk_buff *msg;
+ void *hdr;
+
+ lockdep_assert_held(&drm_fabric_lock);
+
+ msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!msg)
+ return;
+
+ hdr = genlmsg_put(msg, 0, 0, &drm_fabric_nl_family, 0, cmd);
+ if (!hdr) {
+ nlmsg_free(msg);
+ return;
+ }
+
+ if (nla_put_u32(msg, DRM_FABRIC_A_TOPOLOGY_GENERATION, generation) ||
+ drm_fabric_fill_fabric(msg, fabric)) {
+ nlmsg_free(msg);
+ return;
+ }
+
+ genlmsg_end(msg, hdr);
+ genlmsg_multicast(&drm_fabric_nl_family, msg, 0,
+ DRM_FABRIC_NLGRP_MONITOR, GFP_KERNEL);
+}
+
+void drm_fabric_emit_fabric_create(struct drm_fabric *fabric, u32 generation)
+{
+ drm_fabric_fabric_event_send(DRM_FABRIC_CMD_FABRIC_CREATE_NTF, fabric,
+ generation);
+}
+
+void drm_fabric_emit_fabric_delete(struct drm_fabric *fabric, u32 generation)
+{
+ drm_fabric_fabric_event_send(DRM_FABRIC_CMD_FABRIC_DELETE_NTF, fabric,
+ generation);
+}
+
+int drm_fabric_netlink_register(void)
+{
+ return genl_register_family(&drm_fabric_nl_family);
+}
+
+void drm_fabric_netlink_unregister(void)
+{
+ genl_unregister_family(&drm_fabric_nl_family);
+}
diff --git a/drivers/gpu/drm/fabric/drm_fabric_nl.c b/drivers/gpu/drm/fabric/drm_fabric_nl.c
new file mode 100644
index 000000000000..032548405146
--- /dev/null
+++ b/drivers/gpu/drm/fabric/drm_fabric_nl.c
@@ -0,0 +1,125 @@
+// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
+/* Do not edit directly, auto-generated from: */
+/* Documentation/netlink/specs/drm_fabric.yaml */
+/* YNL-GEN kernel source */
+/* To regenerate run: tools/net/ynl/ynl-regen.sh */
+
+#include <net/netlink.h>
+#include <net/genetlink.h>
+
+#include "drm_fabric_nl.h"
+
+#include <uapi/drm/drm_fabric.h>
+
+/* DRM_FABRIC_CMD_FABRIC_GET - do */
+static const struct nla_policy drm_fabric_fabric_get_nl_policy[DRM_FABRIC_A_FABRIC_ID + 1] = {
+ [DRM_FABRIC_A_FABRIC_ID] = { .type = NLA_U32, },
+};
+
+/* DRM_FABRIC_CMD_ENDPOINT_GET - do */
+static const struct nla_policy drm_fabric_endpoint_get_do_nl_policy[DRM_FABRIC_A_BUS_NAME + 1] = {
+ [DRM_FABRIC_A_ENDPOINT_ID] = { .type = NLA_U32, },
+ [DRM_FABRIC_A_DEV_NAME] = { .type = NLA_NUL_STRING, },
+ [DRM_FABRIC_A_BUS_NAME] = { .type = NLA_NUL_STRING, },
+};
+
+/* DRM_FABRIC_CMD_ENDPOINT_GET - dump */
+static const struct nla_policy drm_fabric_endpoint_get_dump_nl_policy[DRM_FABRIC_A_FABRIC_ID + 1] = {
+ [DRM_FABRIC_A_FABRIC_ID] = { .type = NLA_U32, },
+};
+
+/* DRM_FABRIC_CMD_PORT_GET - do */
+static const struct nla_policy drm_fabric_port_get_do_nl_policy[DRM_FABRIC_A_PORT_INDEX + 1] = {
+ [DRM_FABRIC_A_ENDPOINT_ID] = { .type = NLA_U32, },
+ [DRM_FABRIC_A_PORT_INDEX] = { .type = NLA_U32, },
+};
+
+/* DRM_FABRIC_CMD_PORT_GET - dump */
+static const struct nla_policy drm_fabric_port_get_dump_nl_policy[DRM_FABRIC_A_ENDPOINT_ID + 1] = {
+ [DRM_FABRIC_A_ENDPOINT_ID] = { .type = NLA_U32, },
+};
+
+/* DRM_FABRIC_CMD_PORT_STATS_GET - do */
+static const struct nla_policy drm_fabric_port_stats_get_do_nl_policy[DRM_FABRIC_A_PORT_INDEX + 1] = {
+ [DRM_FABRIC_A_ENDPOINT_ID] = { .type = NLA_U32, },
+ [DRM_FABRIC_A_PORT_INDEX] = { .type = NLA_U32, },
+};
+
+/* DRM_FABRIC_CMD_PORT_STATS_GET - dump */
+static const struct nla_policy drm_fabric_port_stats_get_dump_nl_policy[DRM_FABRIC_A_ENDPOINT_ID + 1] = {
+ [DRM_FABRIC_A_ENDPOINT_ID] = { .type = NLA_U32, },
+};
+
+/* Ops table for drm_fabric */
+static const struct genl_split_ops drm_fabric_nl_ops[] = {
+ {
+ .cmd = DRM_FABRIC_CMD_FABRIC_GET,
+ .doit = drm_fabric_nl_fabric_get_doit,
+ .policy = drm_fabric_fabric_get_nl_policy,
+ .maxattr = DRM_FABRIC_A_FABRIC_ID,
+ .flags = GENL_CMD_CAP_DO,
+ },
+ {
+ .cmd = DRM_FABRIC_CMD_FABRIC_GET,
+ .dumpit = drm_fabric_nl_fabric_get_dumpit,
+ .flags = GENL_CMD_CAP_DUMP,
+ },
+ {
+ .cmd = DRM_FABRIC_CMD_ENDPOINT_GET,
+ .doit = drm_fabric_nl_endpoint_get_doit,
+ .policy = drm_fabric_endpoint_get_do_nl_policy,
+ .maxattr = DRM_FABRIC_A_BUS_NAME,
+ .flags = GENL_CMD_CAP_DO,
+ },
+ {
+ .cmd = DRM_FABRIC_CMD_ENDPOINT_GET,
+ .dumpit = drm_fabric_nl_endpoint_get_dumpit,
+ .policy = drm_fabric_endpoint_get_dump_nl_policy,
+ .maxattr = DRM_FABRIC_A_FABRIC_ID,
+ .flags = GENL_CMD_CAP_DUMP,
+ },
+ {
+ .cmd = DRM_FABRIC_CMD_PORT_GET,
+ .doit = drm_fabric_nl_port_get_doit,
+ .policy = drm_fabric_port_get_do_nl_policy,
+ .maxattr = DRM_FABRIC_A_PORT_INDEX,
+ .flags = GENL_CMD_CAP_DO,
+ },
+ {
+ .cmd = DRM_FABRIC_CMD_PORT_GET,
+ .dumpit = drm_fabric_nl_port_get_dumpit,
+ .policy = drm_fabric_port_get_dump_nl_policy,
+ .maxattr = DRM_FABRIC_A_ENDPOINT_ID,
+ .flags = GENL_CMD_CAP_DUMP,
+ },
+ {
+ .cmd = DRM_FABRIC_CMD_PORT_STATS_GET,
+ .doit = drm_fabric_nl_port_stats_get_doit,
+ .policy = drm_fabric_port_stats_get_do_nl_policy,
+ .maxattr = DRM_FABRIC_A_PORT_INDEX,
+ .flags = GENL_CMD_CAP_DO,
+ },
+ {
+ .cmd = DRM_FABRIC_CMD_PORT_STATS_GET,
+ .dumpit = drm_fabric_nl_port_stats_get_dumpit,
+ .policy = drm_fabric_port_stats_get_dump_nl_policy,
+ .maxattr = DRM_FABRIC_A_ENDPOINT_ID,
+ .flags = GENL_CMD_CAP_DUMP,
+ },
+};
+
+static const struct genl_multicast_group drm_fabric_nl_mcgrps[] = {
+ [DRM_FABRIC_NLGRP_MONITOR] = { "monitor", },
+};
+
+struct genl_family drm_fabric_nl_family __ro_after_init = {
+ .name = DRM_FABRIC_FAMILY_NAME,
+ .version = DRM_FABRIC_FAMILY_VERSION,
+ .netnsok = true,
+ .parallel_ops = true,
+ .module = THIS_MODULE,
+ .split_ops = drm_fabric_nl_ops,
+ .n_split_ops = ARRAY_SIZE(drm_fabric_nl_ops),
+ .mcgrps = drm_fabric_nl_mcgrps,
+ .n_mcgrps = ARRAY_SIZE(drm_fabric_nl_mcgrps),
+};
--
2.43.0
next prev parent reply other threads:[~2026-08-24 8:09 UTC|newest]
Thread overview: 9+ 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 ` Konstantin Sinyuk [this message]
2026-08-24 8:09 ` [RFC PATCH 04/12] drm/fabric: add read-only synthetic provider Konstantin Sinyuk
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
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=fea1fb8ce49d7b673a0ccd9e950cdc0fc0565c68.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