[PATCH 4/8] rpmsg: qcom_glink_ssr: Suppress cleanup notify between cluster siblings

From: Shawn Guo

Date: Sat Aug 15 2026 - 09:26:46 EST


Like qcom_sysmon's SSCTL notify, clustered remote processors (e.g.
Nord's HPASS ADSP0/1/2) cannot handle the glink_ssr DO_CLEANUP
broadcast sent to siblings whenever one of them starts or stops,
leaving qcom_glink_ssr_notifier_call() to time out waiting for
CLEANUP_DONE.

Pass the stopping remoteproc's cluster identifier through
qcom_glink_ssr_notify(), and plumb each edge's own cluster identifier
down from the remoteproc that registers it, so that
qcom_glink_ssr_notifier_call() can skip the DO_CLEANUP send when the
sender and the receiving edge share a cluster. As in qcom_sysmon, the
suppression is done on the receiving side, so that a cluster member
keeps sending DO_CLEANUP to, and receiving it from, remote processors
outside its cluster. Edges with no cluster set are unaffected.

The receiving edge's identifier is carried in struct qcom_glink rather
than looked up from the rproc that owns it, so that glink_ssr does not
have to reach into struct remoteproc: RPMSG_QCOM_GLINK does not depend
on REMOTEPROC, and the identifier is deliberately opaque to keep the
two subsystems independent of each other.

Assisted-by: LLM
Signed-off-by: Shawn Guo <shengchao.guo@xxxxxxxxxxxxxxxx>
---
drivers/remoteproc/qcom_common.c | 7 +++---
drivers/remoteproc/qcom_common.h | 2 +-
drivers/rpmsg/qcom_glink_native.c | 20 ++++++++++++++++-
drivers/rpmsg/qcom_glink_native.h | 5 ++++-
drivers/rpmsg/qcom_glink_rpm.c | 2 +-
drivers/rpmsg/qcom_glink_smem.c | 5 +++--
drivers/rpmsg/qcom_glink_ssr.c | 36 ++++++++++++++++++++++++++-----
include/linux/rpmsg/qcom_glink.h | 10 +++++----
8 files changed, 69 insertions(+), 18 deletions(-)

diff --git a/drivers/remoteproc/qcom_common.c b/drivers/remoteproc/qcom_common.c
index 5294e327f158..13458bda6b24 100644
--- a/drivers/remoteproc/qcom_common.c
+++ b/drivers/remoteproc/qcom_common.c
@@ -217,7 +217,8 @@ static int glink_subdev_start(struct rproc_subdev *subdev)
{
struct qcom_rproc_glink *glink = to_glink_subdev(subdev);

- glink->edge = qcom_glink_smem_register(glink->dev, glink->node);
+ glink->edge = qcom_glink_smem_register(&glink->rproc->dev, glink->node,
+ glink->rproc->cluster);

return PTR_ERR_OR_ZERO(glink->edge);
}
@@ -234,7 +235,7 @@ static void glink_subdev_unprepare(struct rproc_subdev *subdev)
{
struct qcom_rproc_glink *glink = to_glink_subdev(subdev);

- qcom_glink_ssr_notify(glink->ssr_name);
+ qcom_glink_ssr_notify(glink->ssr_name, glink->rproc->cluster);
}

/**
@@ -258,7 +259,7 @@ void qcom_add_glink_subdev(struct rproc *rproc, struct qcom_rproc_glink *glink,
return;
}

- glink->dev = dev;
+ glink->rproc = rproc;
glink->subdev.start = glink_subdev_start;
glink->subdev.stop = glink_subdev_stop;
glink->subdev.unprepare = glink_subdev_unprepare;
diff --git a/drivers/remoteproc/qcom_common.h b/drivers/remoteproc/qcom_common.h
index b0e7e336d363..7136494cbfba 100644
--- a/drivers/remoteproc/qcom_common.h
+++ b/drivers/remoteproc/qcom_common.h
@@ -14,7 +14,7 @@ struct qcom_rproc_glink {

const char *ssr_name;

- struct device *dev;
+ struct rproc *rproc;
struct device_node *node;
struct qcom_glink_smem *edge;
};
diff --git a/drivers/rpmsg/qcom_glink_native.c b/drivers/rpmsg/qcom_glink_native.c
index 55793fc18293..06494f4d3a4f 100644
--- a/drivers/rpmsg/qcom_glink_native.c
+++ b/drivers/rpmsg/qcom_glink_native.c
@@ -122,6 +122,8 @@ struct qcom_glink {
unsigned long features;

bool intentless;
+ /* Cluster identifier of the remote processor behind this edge, or NULL */
+ void *cluster;
wait_queue_head_t tx_avail_notify;
bool sent_read_notify;

@@ -1889,7 +1891,8 @@ struct qcom_glink *qcom_glink_native_probe(struct device *dev,
unsigned long features,
struct qcom_glink_pipe *rx,
struct qcom_glink_pipe *tx,
- bool intentless)
+ bool intentless,
+ void *cluster)
{
int ret;
struct qcom_glink *glink;
@@ -1904,6 +1907,7 @@ struct qcom_glink *qcom_glink_native_probe(struct device *dev,

glink->features = features;
glink->intentless = intentless;
+ glink->cluster = cluster;

spin_lock_init(&glink->tx_lock);
spin_lock_init(&glink->rx_lock);
@@ -1937,6 +1941,20 @@ struct qcom_glink *qcom_glink_native_probe(struct device *dev,
}
EXPORT_SYMBOL_GPL(qcom_glink_native_probe);

+/**
+ * qcom_glink_ept_cluster() - cluster identifier of an endpoint's edge
+ * @ept: endpoint to query
+ *
+ * Return: the opaque cluster identifier of the remote processor @ept talks to,
+ * or NULL if it is not part of a cluster.
+ */
+void *qcom_glink_ept_cluster(struct rpmsg_endpoint *ept)
+{
+ struct glink_channel *channel = to_glink_channel(ept);
+
+ return channel->glink->cluster;
+}
+
static int qcom_glink_remove_device(struct device *dev, void *data)
{
device_unregister(dev);
diff --git a/drivers/rpmsg/qcom_glink_native.h b/drivers/rpmsg/qcom_glink_native.h
index 8dbec24de23e..8544f4f5c545 100644
--- a/drivers/rpmsg/qcom_glink_native.h
+++ b/drivers/rpmsg/qcom_glink_native.h
@@ -29,12 +29,15 @@ struct qcom_glink_pipe {

struct device;
struct qcom_glink;
+struct rpmsg_endpoint;

struct qcom_glink *qcom_glink_native_probe(struct device *dev,
unsigned long features,
struct qcom_glink_pipe *rx,
struct qcom_glink_pipe *tx,
- bool intentless);
+ bool intentless,
+ void *cluster);
+void *qcom_glink_ept_cluster(struct rpmsg_endpoint *ept);
void qcom_glink_native_remove(struct qcom_glink *glink);
void qcom_glink_native_rx(struct qcom_glink *glink);

diff --git a/drivers/rpmsg/qcom_glink_rpm.c b/drivers/rpmsg/qcom_glink_rpm.c
index e3ba2c63a5fc..9c1ff05d4a10 100644
--- a/drivers/rpmsg/qcom_glink_rpm.c
+++ b/drivers/rpmsg/qcom_glink_rpm.c
@@ -346,7 +346,7 @@ static int glink_rpm_probe(struct platform_device *pdev)
0,
&rpm->rx_pipe.native,
&rpm->tx_pipe.native,
- true);
+ true, NULL);
if (IS_ERR(glink)) {
mbox_free_channel(rpm->mbox_chan);
return PTR_ERR(glink);
diff --git a/drivers/rpmsg/qcom_glink_smem.c b/drivers/rpmsg/qcom_glink_smem.c
index 35bb03e67ae8..938f9575784e 100644
--- a/drivers/rpmsg/qcom_glink_smem.c
+++ b/drivers/rpmsg/qcom_glink_smem.c
@@ -225,7 +225,8 @@ static void qcom_glink_smem_release(struct device *dev)
}

struct qcom_glink_smem *qcom_glink_smem_register(struct device *parent,
- struct device_node *node)
+ struct device_node *node,
+ void *cluster)
{
struct glink_smem_pipe *rx_pipe;
struct glink_smem_pipe *tx_pipe;
@@ -345,7 +346,7 @@ struct qcom_glink_smem *qcom_glink_smem_register(struct device *parent,
glink = qcom_glink_native_probe(dev,
GLINK_FEATURE_INTENT_REUSE,
&rx_pipe->native, &tx_pipe->native,
- false);
+ false, cluster);
if (IS_ERR(glink)) {
ret = PTR_ERR(glink);
goto err_free_mbox;
diff --git a/drivers/rpmsg/qcom_glink_ssr.c b/drivers/rpmsg/qcom_glink_ssr.c
index e71d3716c55c..74c671a19af0 100644
--- a/drivers/rpmsg/qcom_glink_ssr.c
+++ b/drivers/rpmsg/qcom_glink_ssr.c
@@ -11,6 +11,8 @@
#include <linux/rpmsg/qcom_glink.h>
#include <linux/remoteproc/qcom_rproc.h>

+#include "qcom_glink_native.h"
+
/**
* struct do_cleanup_msg - The data structure for an SSR do_cleanup message
* @version: The G-Link SSR protocol version
@@ -55,16 +57,34 @@ struct glink_ssr {
struct completion completion;
};

+/**
+ * struct glink_ssr_notify_data - payload of an SSR notification
+ * @ssr_name: name of the remoteproc that has been stopped
+ * @cluster: cluster identifier of the remoteproc that has been stopped,
+ * or NULL if it is not part of a cluster
+ */
+struct glink_ssr_notify_data {
+ const char *ssr_name;
+ void *cluster;
+};
+
/* Notifier list for all registered glink_ssr instances */
static BLOCKING_NOTIFIER_HEAD(ssr_notifiers);

/**
* qcom_glink_ssr_notify() - notify GLINK SSR about stopped remoteproc
* @ssr_name: name of the remoteproc that has been stopped
+ * @cluster: cluster identifier of the remoteproc that has been stopped,
+ * or NULL if it is not part of a cluster
*/
-void qcom_glink_ssr_notify(const char *ssr_name)
+void qcom_glink_ssr_notify(const char *ssr_name, void *cluster)
{
- blocking_notifier_call_chain(&ssr_notifiers, 0, (void *)ssr_name);
+ struct glink_ssr_notify_data data = {
+ .ssr_name = ssr_name,
+ .cluster = cluster,
+ };
+
+ blocking_notifier_call_chain(&ssr_notifiers, 0, &data);
}
EXPORT_SYMBOL_GPL(qcom_glink_ssr_notify);

@@ -100,18 +120,24 @@ static int qcom_glink_ssr_notifier_call(struct notifier_block *nb,
void *data)
{
struct glink_ssr *ssr = container_of(nb, struct glink_ssr, nb);
+ struct glink_ssr_notify_data *notify_data = data;
struct do_cleanup_msg msg;
- char *ssr_name = data;
+ void *cluster;
int ret;

+ /* Cluster siblings' firmware can't handle peer SSR notify; skip it */
+ cluster = qcom_glink_ept_cluster(ssr->ept);
+ if (cluster && cluster == notify_data->cluster)
+ return NOTIFY_DONE;
+
ssr->seq_num++;
reinit_completion(&ssr->completion);

memset(&msg, 0, sizeof(msg));
msg.command = cpu_to_le32(GLINK_SSR_DO_CLEANUP);
msg.seq_num = cpu_to_le32(ssr->seq_num);
- msg.name_len = cpu_to_le32(strlen(ssr_name));
- strscpy(msg.name, ssr_name, sizeof(msg.name));
+ msg.name_len = cpu_to_le32(strlen(notify_data->ssr_name));
+ strscpy(msg.name, notify_data->ssr_name, sizeof(msg.name));

ret = rpmsg_send(ssr->ept, &msg, sizeof(msg));
if (ret < 0)
diff --git a/include/linux/rpmsg/qcom_glink.h b/include/linux/rpmsg/qcom_glink.h
index bfbd48f435fa..6591d016e8cb 100644
--- a/include/linux/rpmsg/qcom_glink.h
+++ b/include/linux/rpmsg/qcom_glink.h
@@ -8,22 +8,24 @@
struct qcom_glink_smem;

#if IS_ENABLED(CONFIG_RPMSG_QCOM_GLINK)
-void qcom_glink_ssr_notify(const char *ssr_name);
+void qcom_glink_ssr_notify(const char *ssr_name, void *cluster);
#else
-static inline void qcom_glink_ssr_notify(const char *ssr_name) {}
+static inline void qcom_glink_ssr_notify(const char *ssr_name, void *cluster) {}
#endif

#if IS_ENABLED(CONFIG_RPMSG_QCOM_GLINK_SMEM)

struct qcom_glink_smem *qcom_glink_smem_register(struct device *parent,
- struct device_node *node);
+ struct device_node *node,
+ void *cluster);
void qcom_glink_smem_unregister(struct qcom_glink_smem *glink);

#else

static inline struct qcom_glink_smem *
qcom_glink_smem_register(struct device *parent,
- struct device_node *node)
+ struct device_node *node,
+ void *cluster)
{
return NULL;
}
--
2.43.0