[RFC PATCH 09/12] drm/fabric: implement mutation netlink operations

Konstantin Sinyuk <[email protected]>
Newsgroups org.kernel.vger.linux-doc,org.freedesktop.lists.dri-devel,org.kernel.vger.linux-kernel,org.kernel.vger.linux-kselftest,org.kernel.vger.netdev
Message-ID <5b008798364477a2740ebb18544bddfa16f7d512.1787552412.git.ksinyuk@kernel.org>
Connect the six provisioning operations to the core mutation helpers and
add the generated operation and policy source that dispatches to them.

Resolve and pin mutation targets in pre_doit, run each transaction under
drm_fabric_mutation_lock, call the provider without drm_fabric_lock held,
and release references in post_doit. All six require GENL_ADMIN_PERM and
are confined to init_net.

A failed provider callback returns its error with core state unchanged.
Successful mutations emit notifications after commit, carrying the
resulting topology generation.

Co-developed-by: Ilia Levi <[email protected]>
Signed-off-by: Ilia Levi <[email protected]>
Signed-off-by: Konstantin Sinyuk <[email protected]>
Assisted-by: GitHub-Copilot:claude-opus-4.8
---
 drivers/gpu/drm/fabric/drm_fabric.c         |   5 +-
 drivers/gpu/drm/fabric/drm_fabric_netlink.c | 275 ++++++++++++++++++++
 drivers/gpu/drm/fabric/drm_fabric_nl.c      | 106 ++++++++
 3 files changed, 384 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/fabric/drm_fabric.c b/drivers/gpu/drm/fabric/drm_fabric.c
index 1aae5ff68798..50c34b4fd722 100644
--- a/drivers/gpu/drm/fabric/drm_fabric.c
+++ b/drivers/gpu/drm/fabric/drm_fabric.c
@@ -793,7 +793,8 @@ int drm_fabric_user_fabric_del(u32 fabric_id)
 		/* Symmetric with FABRIC_NEW: only an empty fabric may be removed. */
 		if (drm_fabric_has_members(fabric))
 			return -EBUSY;
-		drm_fabric_base_seq_inc();
+		/* Notify while the userspace-owned fabric is still addressable by id. */
+		drm_fabric_emit_fabric_delete(fabric, drm_fabric_base_seq_inc());
 		xa_erase(&drm_fabric_xa, fabric->id);
 	}
 
@@ -906,7 +907,7 @@ int drm_fabric_endpoint_set(struct drm_fabric_endpoint *ep,
 		if (change.valid & DRM_FABRIC_EP_CHANGE_ADMIN)
 			ep->admin_state = change.admin;
 
-		drm_fabric_base_seq_inc();
+		drm_fabric_emit_endpoint_change(ep, drm_fabric_base_seq_inc());
 	}
 
 	return 0;
diff --git a/drivers/gpu/drm/fabric/drm_fabric_netlink.c b/drivers/gpu/drm/fabric/drm_fabric_netlink.c
index fef2d4c8f5bb..1a0f293723e9 100644
--- a/drivers/gpu/drm/fabric/drm_fabric_netlink.c
+++ b/drivers/gpu/drm/fabric/drm_fabric_netlink.c
@@ -80,6 +80,7 @@ static int drm_fabric_fill_endpoint(struct sk_buff *skb,
 	    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_u32(skb, DRM_FABRIC_A_ENDPOINT_ATTRS_ADMIN_STATE, ep->admin_state) ||
 	    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,
@@ -132,6 +133,8 @@ static int drm_fabric_fill_port(struct sk_buff *skb,
 	    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_ADMIN_STATE,
+			port->admin_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,
@@ -846,6 +849,272 @@ int drm_fabric_nl_port_stats_get_dumpit(struct sk_buff *skb,
 	return ret;
 }
 
+/* Fabric create/delete have no target to resolve; only serialize mutation. */
+int drm_fabric_nl_pre_doit(const struct genl_split_ops *ops,
+			   struct sk_buff *skb, struct genl_info *info)
+{
+	int ret = drm_fabric_nl_host_only(genl_info_net(info));
+
+	if (ret)
+		return ret;
+
+	mutex_lock(&drm_fabric_mutation_lock);
+	return 0;
+}
+
+void drm_fabric_nl_post_doit(const struct genl_split_ops *ops,
+			     struct sk_buff *skb, struct genl_info *info)
+{
+	mutex_unlock(&drm_fabric_mutation_lock);
+}
+
+/*
+ * Pin the target in user_ptr[0]. Drop the mutation lock on failure because
+ * post_doit does not run when pre_doit fails.
+ */
+int drm_fabric_nl_endpoint_pre_doit(const struct genl_split_ops *ops,
+				    struct sk_buff *skb, struct genl_info *info)
+{
+	struct drm_fabric_endpoint *ep;
+	int ret = drm_fabric_nl_host_only(genl_info_net(info));
+
+	if (ret)
+		return ret;
+
+	mutex_lock(&drm_fabric_mutation_lock);
+
+	scoped_guard(mutex, &drm_fabric_lock) {
+		ep = drm_fabric_resolve_endpoint(info);
+		if (!IS_ERR(ep))
+			drm_fabric_endpoint_get(ep);
+	}
+
+	if (IS_ERR(ep)) {
+		mutex_unlock(&drm_fabric_mutation_lock);
+		return PTR_ERR(ep);
+	}
+
+	info->user_ptr[0] = ep;
+	return 0;
+}
+
+void drm_fabric_nl_endpoint_post_doit(const struct genl_split_ops *ops,
+				      struct sk_buff *skb,
+				      struct genl_info *info)
+{
+	drm_fabric_endpoint_put(info->user_ptr[0]);
+	mutex_unlock(&drm_fabric_mutation_lock);
+}
+
+int drm_fabric_nl_port_pre_doit(const struct genl_split_ops *ops,
+				struct sk_buff *skb, struct genl_info *info)
+{
+	struct drm_fabric_port *port;
+	u32 ep_id, port_idx;
+	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;
+
+	mutex_lock(&drm_fabric_mutation_lock);
+
+	port = drm_fabric_port_find_get(ep_id, port_idx);
+	if (IS_ERR(port)) {
+		mutex_unlock(&drm_fabric_mutation_lock);
+		return PTR_ERR(port);
+	}
+
+	info->user_ptr[0] = port;
+	return 0;
+}
+
+void drm_fabric_nl_port_post_doit(const struct genl_split_ops *ops,
+				  struct sk_buff *skb, struct genl_info *info)
+{
+	drm_fabric_port_put(info->user_ptr[0]);
+	mutex_unlock(&drm_fabric_mutation_lock);
+}
+
+/* A nested policy cannot mark members required; check type and instance-id here. */
+static int drm_fabric_parse_new_params(struct genl_info *info,
+					enum drm_fabric_type *type,
+					const char **name, u64 *instance_id)
+{
+	struct nlattr *pa[DRM_FABRIC_A_FABRIC_NEW_PARAMS_MAX + 1];
+	struct nlattr *nest;
+	int ret;
+
+	if (GENL_REQ_ATTR_CHECK(info, DRM_FABRIC_A_FABRIC_NEW_PARAMS))
+		return -EINVAL;
+
+	nest = info->attrs[DRM_FABRIC_A_FABRIC_NEW_PARAMS];
+	ret = nla_parse_nested(pa, DRM_FABRIC_A_FABRIC_NEW_PARAMS_MAX, nest,
+			       drm_fabric_fabric_new_params_nl_policy,
+			       info->extack);
+	if (ret)
+		return ret;
+
+	if (NL_REQ_ATTR_CHECK(info->extack, nest, pa,
+			      DRM_FABRIC_A_FABRIC_NEW_PARAMS_TYPE) ||
+	    NL_REQ_ATTR_CHECK(info->extack, nest, pa,
+			      DRM_FABRIC_A_FABRIC_NEW_PARAMS_INSTANCE_ID))
+		return -EINVAL;
+
+	*type = nla_get_u32(pa[DRM_FABRIC_A_FABRIC_NEW_PARAMS_TYPE]);
+	*instance_id = nla_get_u64(pa[DRM_FABRIC_A_FABRIC_NEW_PARAMS_INSTANCE_ID]);
+	*name = pa[DRM_FABRIC_A_FABRIC_NEW_PARAMS_NAME] ?
+		nla_data(pa[DRM_FABRIC_A_FABRIC_NEW_PARAMS_NAME]) : NULL;
+	return 0;
+}
+
+int drm_fabric_nl_fabric_new_doit(struct sk_buff *skb,
+				  struct genl_info *info)
+{
+	enum drm_fabric_type type;
+	const char *name = NULL;
+	u64 instance_id;
+	struct sk_buff *msg;
+	struct nlattr *id_attr;
+	u32 fabric_id;
+	void *hdr;
+	int ret;
+
+	ret = drm_fabric_parse_new_params(info, &type, &name, &instance_id);
+	if (ret)
+		return ret;
+
+	/*
+	 * Reserve the id attribute before publishing: with the space already
+	 * committed the store cannot fail, so there is no create-then-withdraw
+	 * window.
+	 */
+	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_NEW);
+	if (!hdr) {
+		nlmsg_free(msg);
+		return -EMSGSIZE;
+	}
+
+	id_attr = nla_reserve(msg, DRM_FABRIC_A_FABRIC_ID, sizeof(u32));
+	if (!id_attr) {
+		nlmsg_free(msg);
+		return -EMSGSIZE;
+	}
+
+	ret = drm_fabric_user_fabric_new(type, instance_id, name, &fabric_id);
+	if (ret) {
+		nlmsg_free(msg);
+		return ret;
+	}
+
+	/* nla_put_u32() copies a host-order u32 verbatim; so does this store. */
+	*(u32 *)nla_data(id_attr) = fabric_id;
+
+	genlmsg_end(msg, hdr);
+	return genlmsg_reply(msg, info);
+}
+
+int drm_fabric_nl_fabric_del_doit(struct sk_buff *skb,
+				  struct genl_info *info)
+{
+	u32 fabric_id;
+
+	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]);
+	return drm_fabric_user_fabric_del(fabric_id);
+}
+
+int drm_fabric_nl_endpoint_set_doit(struct sk_buff *skb,
+				    struct genl_info *info)
+{
+	struct drm_fabric_endpoint *ep = info->user_ptr[0];
+	struct drm_fabric_endpoint_change change = {};
+
+	if (info->attrs[DRM_FABRIC_A_FABRIC_ID]) {
+		change.valid |= DRM_FABRIC_EP_CHANGE_FABRIC;
+		change.fabric_id =
+			nla_get_u32(info->attrs[DRM_FABRIC_A_FABRIC_ID]);
+	}
+
+	if (info->attrs[DRM_FABRIC_A_ADMIN_STATE]) {
+		change.valid |= DRM_FABRIC_EP_CHANGE_ADMIN;
+		change.admin =
+			nla_get_u32(info->attrs[DRM_FABRIC_A_ADMIN_STATE]);
+	}
+
+	if (!change.valid)
+		return -EINVAL;
+
+	return drm_fabric_endpoint_set(ep, &change);
+}
+
+int drm_fabric_nl_port_set_doit(struct sk_buff *skb,
+				struct genl_info *info)
+{
+	struct drm_fabric_port *port = info->user_ptr[0];
+	enum drm_fabric_admin_state admin;
+
+	if (GENL_REQ_ATTR_CHECK(info, DRM_FABRIC_A_ADMIN_STATE))
+		return -EINVAL;
+
+	admin = nla_get_u32(info->attrs[DRM_FABRIC_A_ADMIN_STATE]);
+
+	return drm_fabric_port_set_admin(port, admin);
+}
+
+int drm_fabric_nl_port_peer_new_doit(struct sk_buff *skb,
+				     struct genl_info *info)
+{
+	struct drm_fabric_port *port = info->user_ptr[0];
+	struct nlattr *pa[DRM_FABRIC_A_PEER_ATTRS_MAX + 1];
+	struct drm_fabric_peer peer = {};
+	int ret;
+
+	if (GENL_REQ_ATTR_CHECK(info, DRM_FABRIC_A_PEER))
+		return -EINVAL;
+
+	ret = nla_parse_nested(pa, DRM_FABRIC_A_PEER_ATTRS_MAX,
+			       info->attrs[DRM_FABRIC_A_PEER],
+			       drm_fabric_peer_nl_policy, info->extack);
+	if (ret)
+		return ret;
+
+	/*
+	 * A nested policy cannot require members; require the complete peer
+	 * descriptor here.
+	 */
+	if (!pa[DRM_FABRIC_A_PEER_ATTRS_PEER_ID] ||
+	    !pa[DRM_FABRIC_A_PEER_ATTRS_TYPE] ||
+	    !pa[DRM_FABRIC_A_PEER_ATTRS_PORT_INDEX])
+		return -EINVAL;
+
+	peer.peer_id = nla_get_u64(pa[DRM_FABRIC_A_PEER_ATTRS_PEER_ID]);
+	peer.peer_type = nla_get_u32(pa[DRM_FABRIC_A_PEER_ATTRS_TYPE]);
+	peer.port_index = nla_get_u32(pa[DRM_FABRIC_A_PEER_ATTRS_PORT_INDEX]);
+
+	return drm_fabric_port_peer_new(port, &peer);
+}
+
+int drm_fabric_nl_port_peer_del_doit(struct sk_buff *skb,
+				     struct genl_info *info)
+{
+	struct drm_fabric_port *port = info->user_ptr[0];
+
+	return drm_fabric_port_peer_del(port);
+}
+
 void drm_fabric_emit_port_change(struct drm_fabric_port *port, u32 generation)
 {
 	struct sk_buff *msg;
@@ -982,6 +1251,12 @@ void drm_fabric_emit_endpoint_delete(struct drm_fabric_endpoint *ep, u32 generat
 				       generation);
 }
 
+void drm_fabric_emit_endpoint_change(struct drm_fabric_endpoint *ep, u32 generation)
+{
+	drm_fabric_endpoint_event_send(DRM_FABRIC_CMD_ENDPOINT_CHANGE_NTF, ep,
+				       generation);
+}
+
 static void drm_fabric_fabric_event_send(enum drm_fabric_cmd cmd,
 					 struct drm_fabric *fabric,
 					 u32 generation)
diff --git a/drivers/gpu/drm/fabric/drm_fabric_nl.c b/drivers/gpu/drm/fabric/drm_fabric_nl.c
index 032548405146..20b277f27ae9 100644
--- a/drivers/gpu/drm/fabric/drm_fabric_nl.c
+++ b/drivers/gpu/drm/fabric/drm_fabric_nl.c
@@ -11,6 +11,19 @@
 
 #include <uapi/drm/drm_fabric.h>
 
+/* Common nested types */
+const struct nla_policy drm_fabric_fabric_new_params_nl_policy[DRM_FABRIC_A_FABRIC_NEW_PARAMS_INSTANCE_ID + 1] = {
+	[DRM_FABRIC_A_FABRIC_NEW_PARAMS_TYPE] = NLA_POLICY_RANGE(NLA_U32, 1, 1),
+	[DRM_FABRIC_A_FABRIC_NEW_PARAMS_NAME] = { .type = NLA_NUL_STRING, .len = 31, },
+	[DRM_FABRIC_A_FABRIC_NEW_PARAMS_INSTANCE_ID] = { .type = NLA_U64, },
+};
+
+const struct nla_policy drm_fabric_peer_nl_policy[DRM_FABRIC_A_PEER_ATTRS_PORT_INDEX + 1] = {
+	[DRM_FABRIC_A_PEER_ATTRS_PEER_ID] = { .type = NLA_U64, },
+	[DRM_FABRIC_A_PEER_ATTRS_TYPE] = NLA_POLICY_RANGE(NLA_U32, 1, 2),
+	[DRM_FABRIC_A_PEER_ATTRS_PORT_INDEX] = { .type = NLA_U32, },
+};
+
 /* 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, },
@@ -50,6 +63,45 @@ static const struct nla_policy drm_fabric_port_stats_get_dump_nl_policy[DRM_FABR
 	[DRM_FABRIC_A_ENDPOINT_ID] = { .type = NLA_U32, },
 };
 
+/* DRM_FABRIC_CMD_FABRIC_NEW - do */
+static const struct nla_policy drm_fabric_fabric_new_nl_policy[DRM_FABRIC_A_FABRIC_NEW_PARAMS + 1] = {
+	[DRM_FABRIC_A_FABRIC_NEW_PARAMS] = NLA_POLICY_NESTED(drm_fabric_fabric_new_params_nl_policy),
+};
+
+/* DRM_FABRIC_CMD_FABRIC_DEL - do */
+static const struct nla_policy drm_fabric_fabric_del_nl_policy[DRM_FABRIC_A_FABRIC_ID + 1] = {
+	[DRM_FABRIC_A_FABRIC_ID] = { .type = NLA_U32, },
+};
+
+/* DRM_FABRIC_CMD_ENDPOINT_SET - do */
+static const struct nla_policy drm_fabric_endpoint_set_nl_policy[DRM_FABRIC_A_ADMIN_STATE + 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_A_FABRIC_ID] = { .type = NLA_U32, },
+	[DRM_FABRIC_A_ADMIN_STATE] = NLA_POLICY_RANGE(NLA_U32, 1, 2),
+};
+
+/* DRM_FABRIC_CMD_PORT_SET - do */
+static const struct nla_policy drm_fabric_port_set_nl_policy[DRM_FABRIC_A_ADMIN_STATE + 1] = {
+	[DRM_FABRIC_A_ENDPOINT_ID] = { .type = NLA_U32, },
+	[DRM_FABRIC_A_PORT_INDEX] = { .type = NLA_U32, },
+	[DRM_FABRIC_A_ADMIN_STATE] = NLA_POLICY_RANGE(NLA_U32, 1, 2),
+};
+
+/* DRM_FABRIC_CMD_PORT_PEER_NEW - do */
+static const struct nla_policy drm_fabric_port_peer_new_nl_policy[DRM_FABRIC_A_PEER + 1] = {
+	[DRM_FABRIC_A_ENDPOINT_ID] = { .type = NLA_U32, },
+	[DRM_FABRIC_A_PORT_INDEX] = { .type = NLA_U32, },
+	[DRM_FABRIC_A_PEER] = NLA_POLICY_NESTED(drm_fabric_peer_nl_policy),
+};
+
+/* DRM_FABRIC_CMD_PORT_PEER_DEL - do */
+static const struct nla_policy drm_fabric_port_peer_del_nl_policy[DRM_FABRIC_A_PORT_INDEX + 1] = {
+	[DRM_FABRIC_A_ENDPOINT_ID] = { .type = NLA_U32, },
+	[DRM_FABRIC_A_PORT_INDEX] = { .type = NLA_U32, },
+};
+
 /* Ops table for drm_fabric */
 static const struct genl_split_ops drm_fabric_nl_ops[] = {
 	{
@@ -106,6 +158,60 @@ static const struct genl_split_ops drm_fabric_nl_ops[] = {
 		.maxattr	= DRM_FABRIC_A_ENDPOINT_ID,
 		.flags		= GENL_CMD_CAP_DUMP,
 	},
+	{
+		.cmd		= DRM_FABRIC_CMD_FABRIC_NEW,
+		.pre_doit	= drm_fabric_nl_pre_doit,
+		.doit		= drm_fabric_nl_fabric_new_doit,
+		.post_doit	= drm_fabric_nl_post_doit,
+		.policy		= drm_fabric_fabric_new_nl_policy,
+		.maxattr	= DRM_FABRIC_A_FABRIC_NEW_PARAMS,
+		.flags		= GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
+	},
+	{
+		.cmd		= DRM_FABRIC_CMD_FABRIC_DEL,
+		.pre_doit	= drm_fabric_nl_pre_doit,
+		.doit		= drm_fabric_nl_fabric_del_doit,
+		.post_doit	= drm_fabric_nl_post_doit,
+		.policy		= drm_fabric_fabric_del_nl_policy,
+		.maxattr	= DRM_FABRIC_A_FABRIC_ID,
+		.flags		= GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
+	},
+	{
+		.cmd		= DRM_FABRIC_CMD_ENDPOINT_SET,
+		.pre_doit	= drm_fabric_nl_endpoint_pre_doit,
+		.doit		= drm_fabric_nl_endpoint_set_doit,
+		.post_doit	= drm_fabric_nl_endpoint_post_doit,
+		.policy		= drm_fabric_endpoint_set_nl_policy,
+		.maxattr	= DRM_FABRIC_A_ADMIN_STATE,
+		.flags		= GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
+	},
+	{
+		.cmd		= DRM_FABRIC_CMD_PORT_SET,
+		.pre_doit	= drm_fabric_nl_port_pre_doit,
+		.doit		= drm_fabric_nl_port_set_doit,
+		.post_doit	= drm_fabric_nl_port_post_doit,
+		.policy		= drm_fabric_port_set_nl_policy,
+		.maxattr	= DRM_FABRIC_A_ADMIN_STATE,
+		.flags		= GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
+	},
+	{
+		.cmd		= DRM_FABRIC_CMD_PORT_PEER_NEW,
+		.pre_doit	= drm_fabric_nl_port_pre_doit,
+		.doit		= drm_fabric_nl_port_peer_new_doit,
+		.post_doit	= drm_fabric_nl_port_post_doit,
+		.policy		= drm_fabric_port_peer_new_nl_policy,
+		.maxattr	= DRM_FABRIC_A_PEER,
+		.flags		= GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
+	},
+	{
+		.cmd		= DRM_FABRIC_CMD_PORT_PEER_DEL,
+		.pre_doit	= drm_fabric_nl_port_pre_doit,
+		.doit		= drm_fabric_nl_port_peer_del_doit,
+		.post_doit	= drm_fabric_nl_port_post_doit,
+		.policy		= drm_fabric_port_peer_del_nl_policy,
+		.maxattr	= DRM_FABRIC_A_PORT_INDEX,
+		.flags		= GENL_ADMIN_PERM | GENL_CMD_CAP_DO,
+	},
 };
 
 static const struct genl_multicast_group drm_fabric_nl_mcgrps[] = {
-- 
2.43.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.