[PATCH ath-next 2/2] wifi: ath12k: Share RootPD state across UserPDs to avoid duplicate operations

Aaradhana Sahu aaradhana.sahu at oss.qualcomm.com
Mon Jul 20 23:50:38 PDT 2026


Currently, each ath12k AHB device maintains its own RootPD-related
information. However, RootPD is shared across all UserPD devices, so
RootPD-related operations such as RootPD boot, and notifier registration,
should be performed only once during the first UserPD boot up.

Due to per-device RootPD information, the driver is unable to track
shared RootPD state across multiple UserPDs, which can result in these
operations being performed multiple times.

Fix this by introducing a new ath12k_ahb_rproc_info structure to hold
shared RootPD-related information such as notifier callbacks, boot
state, and number of userPD.

Allocate this structure during the first device probe in
ath12k_ahb_rproc_info_alloc() and reuse the same structure for all
subsequent device probes.

Also handle rproc deconfiguration correctly when multiple UserPDs share a
common RootPD. The RootPD provides shared firmware services and resources
for all UserPDs. Therefore, do not shut down the RootPD while any UserPD
remains powered on or is still in the boot process.

In addition, a UserPD can be powered down before its associated resources
are fully released. Defer g_rproc_info cleanup until all UserPD-related
state and resources have been cleaned up.

For intermediate UserPD removal, cleanup only per-device information
and remove the UserPD from the tracking array while keeping the RootPD
running for remaining active UserPDs.

Note: UserPD IDs start from 1, as ID 0 is used by RootPD, which is
completely handled by the remoteproc driver.

The multi-PD architecture on AHB platforms operates as follows:

                     +-----------------------------+
                     |   Q6 RootPD (rproc)         |
                     |   (Shared Resource)         |
                     |                             |
                     |  - Manages UserPD lifecycle |
                     |  - Provides SSR notifiers   |
                     +--------------+--------------+
                                    |
                                    | Manages
                                    |
              +---------------------+---------------------+
              |                     |                     |
         +----v----+           +----v----+          +----v----+
         | UserPD1 |           | UserPD2 |          | UserPD3 |
         |  ID=1   |           |  ID=2   |          |  ID=3   |
         | (Radio) |           | (Radio) |          | (Radio) |
         +---------+           +---------+          +---------+
              |                     |                     |
              |                     |                     |
         ath12k_ahb           ath12k_ahb           ath12k_ahb
         (device 1)           (device 2)           (device 3)
              |                     |                     |
              +---------------------+---------------------+
                                    |
                                    | All reference
                                    |
                          +---------v----------+
                          | ath12k_ahb_rproc_  |
                          | info (shared)      |
                          |                    |
                          | - tgt_rproc        |
                          | - notifiers        |
                          | - rootpd_ready     |
                          | - num_userpd       |
                          | - userpd[] array   |
                          +--------------------+

Tested-on: IPQ5332 hw1.0 AHB WLAN.WBE.1.6-01275-QCAHKSWPL_SILICONZ-1

Signed-off-by: Aaradhana Sahu <aaradhana.sahu at oss.qualcomm.com>
---
 drivers/net/wireless/ath/ath12k/ahb.c | 205 +++++++++++++++++++++-----
 drivers/net/wireless/ath/ath12k/ahb.h |  15 +-
 2 files changed, 177 insertions(+), 43 deletions(-)

diff --git a/drivers/net/wireless/ath/ath12k/ahb.c b/drivers/net/wireless/ath/ath12k/ahb.c
index 14ee696960c7..0fc55c9169e1 100644
--- a/drivers/net/wireless/ath/ath12k/ahb.c
+++ b/drivers/net/wireless/ath/ath12k/ahb.c
@@ -25,6 +25,22 @@ static const char ath12k_userpd_irq[][9] = {"spawn",
 				     "ready",
 				     "stop-ack"};
 
+/*
+ * Multi-UserPD Architecture:
+ *
+ * One Q6 RootPD (managed by separate rproc driver) supports multiple
+ * ath12k UserPDs. Each UserPD represents a WiFi radio instance.
+ *
+ * Lifecycle:
+ *  - RootPD boots when first UserPD probes
+ *  - All UserPDs share RootPD's SSR notifier
+ *
+ * Locking:
+ *  - ath12k_rproc_info_lock: Protects g_rproc_info allocation/free
+ */
+static struct ath12k_ahb_rproc_info *g_rproc_info;
+static DEFINE_MUTEX(ath12k_rproc_info_lock);
+
 static const char *irq_name[ATH12K_IRQ_NUM_MAX] = {
 	"misc-pulse1",
 	"misc-latch",
@@ -786,44 +802,85 @@ static int ath12k_ahb_config_rproc_irq(struct ath12k_base *ab)
 static int ath12k_ahb_root_pd_state_notifier(struct notifier_block *nb,
 					     const unsigned long event, void *data)
 {
-	struct ath12k_ahb *ab_ahb = container_of(nb, struct ath12k_ahb, root_pd_nb);
-	struct ath12k_base *ab = ab_ahb->ab;
+	struct ath12k_ahb_rproc_info *rproc_info =
+		container_of(nb, struct ath12k_ahb_rproc_info, root_pd_nb);
 
 	if (event == ATH12K_RPROC_AFTER_POWERUP) {
-		ath12k_dbg(ab, ATH12K_DBG_AHB, "Root PD is UP\n");
-		complete(&ab_ahb->rootpd_ready);
+		ath12k_generic_dbg(ATH12K_DBG_AHB, "Root PD is UP\n");
+		complete(&rproc_info->rootpd_ready);
 	}
 
 	return 0;
 }
 
-static int ath12k_ahb_register_rproc_notifier(struct ath12k_base *ab)
+static int ath12k_ahb_register_rproc_notifier(void)
 {
-	struct ath12k_ahb *ab_ahb = ath12k_ab_to_ahb(ab);
+	int ret;
+
+	lockdep_assert_held(&ath12k_rproc_info_lock);
 
-	ab_ahb->root_pd_nb.notifier_call = ath12k_ahb_root_pd_state_notifier;
-	init_completion(&ab_ahb->rootpd_ready);
+	if (g_rproc_info->root_pd_notifier)
+		return 0;
 
-	ab_ahb->root_pd_notifier = qcom_register_ssr_notifier(ab_ahb->tgt_rproc->name,
-							      &ab_ahb->root_pd_nb);
-	if (IS_ERR(ab_ahb->root_pd_notifier))
-		return PTR_ERR(ab_ahb->root_pd_notifier);
+	g_rproc_info->root_pd_nb.notifier_call = ath12k_ahb_root_pd_state_notifier;
+
+	g_rproc_info->root_pd_notifier =
+			qcom_register_ssr_notifier(g_rproc_info->tgt_rproc->name,
+						   &g_rproc_info->root_pd_nb);
+	if (IS_ERR(g_rproc_info->root_pd_notifier)) {
+		ret = PTR_ERR(g_rproc_info->root_pd_notifier);
+		g_rproc_info->root_pd_notifier = NULL;
+		return ret;
+	}
 
 	return 0;
 }
 
-static void ath12k_ahb_unregister_rproc_notifier(struct ath12k_base *ab)
+static void ath12k_ahb_unregister_rproc_notifier(void)
+{
+	lockdep_assert_held(&ath12k_rproc_info_lock);
+
+	if (!g_rproc_info->root_pd_notifier)
+		return;
+
+	qcom_unregister_ssr_notifier(g_rproc_info->root_pd_notifier,
+				     &g_rproc_info->root_pd_nb);
+	g_rproc_info->root_pd_notifier = NULL;
+}
+
+static void ath12k_ahb_cleanup_userpd(struct ath12k_base *ab)
 {
 	struct ath12k_ahb *ab_ahb = ath12k_ab_to_ahb(ab);
+	struct ath12k_ahb_rproc_info *rproc_info = ab_ahb->rproc_info;
 
-	if (!ab_ahb->root_pd_notifier) {
-		ath12k_err(ab, "Rproc notifier not registered\n");
+	lockdep_assert_held(&ath12k_rproc_info_lock);
+
+	if (!rproc_info)
 		return;
-	}
 
-	qcom_unregister_ssr_notifier(ab_ahb->root_pd_notifier,
-				     &ab_ahb->root_pd_nb);
-	ab_ahb->root_pd_notifier = NULL;
+	rproc_info->userpd[ab_ahb->userpd_id - 1] = NULL;
+	rproc_info->num_userpd--;
+	ab_ahb->rproc_info = NULL;
+}
+
+static struct ath12k_ahb_rproc_info *ath12k_ahb_rproc_info_alloc(struct ath12k_base *ab)
+{
+	struct ath12k_ahb *ab_ahb = ath12k_ab_to_ahb(ab);
+	struct ath12k_ahb_rproc_info *rproc_info;
+
+	lockdep_assert_held(&ath12k_rproc_info_lock);
+
+	rproc_info = kzalloc_obj(*rproc_info, GFP_KERNEL);
+	if (!rproc_info)
+		return NULL;
+
+	rproc_info->rootpd_booted_by_driver = false;
+	rproc_info->userpd[ab_ahb->userpd_id - 1] = ab_ahb;
+	rproc_info->num_userpd = 1;
+	init_completion(&rproc_info->rootpd_ready);
+	ab_ahb->rproc_info = rproc_info;
+
+	return rproc_info;
 }
 
 static int ath12k_ahb_get_rproc(struct ath12k_base *ab)
@@ -832,37 +889,69 @@ static int ath12k_ahb_get_rproc(struct ath12k_base *ab)
 	struct device *dev = ab->dev;
 	struct device_node *np;
 	struct rproc *prproc;
+	int ret;
+
+	lockdep_assert_held(&ath12k_rproc_info_lock);
+
+	if (ab_ahb->userpd_id > ATH12K_MAX_DEVICES)
+		return -ENOSPC;
+
+	if (g_rproc_info) {
+		if (g_rproc_info->num_userpd >= ATH12K_MAX_DEVICES) {
+			ath12k_err(ab, "Max UserPD limit reached\n");
+			return -ENOSPC;
+		}
+
+		g_rproc_info->userpd[ab_ahb->userpd_id - 1] = ab_ahb;
+		g_rproc_info->num_userpd++;
+		ab_ahb->rproc_info = g_rproc_info;
+		return 0;
+	}
+
+	g_rproc_info = ath12k_ahb_rproc_info_alloc(ab);
+	if (!g_rproc_info)
+		return -ENOMEM;
 
 	np = of_parse_phandle(dev->of_node, "qcom,rproc", 0);
 	if (!np) {
 		ath12k_err(ab, "failed to get q6_rproc handle\n");
-		return -ENOENT;
+		ret = -ENOENT;
+		goto err_free_rproc_info;
 	}
 
 	prproc = rproc_get_by_phandle(np->phandle);
 	of_node_put(np);
-	if (!prproc)
-		return dev_err_probe(&ab->pdev->dev, -EPROBE_DEFER,
-				     "failed to get rproc\n");
-
-	ab_ahb->tgt_rproc = prproc;
+	if (!prproc) {
+		ret = dev_err_probe(&ab->pdev->dev, -EPROBE_DEFER,
+				    "failed to get rproc\n");
+		goto err_free_rproc_info;
+	}
 
+	g_rproc_info->tgt_rproc = prproc;
 	return 0;
+
+err_free_rproc_info:
+	ab_ahb->rproc_info = NULL;
+	kfree(g_rproc_info);
+	g_rproc_info = NULL;
+	return ret;
 }
 
 static int ath12k_ahb_boot_root_pd(struct ath12k_base *ab)
 {
-	struct ath12k_ahb *ab_ahb = ath12k_ab_to_ahb(ab);
 	unsigned long time_left;
 	int ret;
 
-	ret = rproc_boot(ab_ahb->tgt_rproc);
+	lockdep_assert_held(&ath12k_rproc_info_lock);
+	reinit_completion(&g_rproc_info->rootpd_ready);
+
+	ret = rproc_boot(g_rproc_info->tgt_rproc);
 	if (ret < 0) {
 		ath12k_err(ab, "RootPD boot failed\n");
 		return ret;
 	}
 
-	time_left = wait_for_completion_timeout(&ab_ahb->rootpd_ready,
+	time_left = wait_for_completion_timeout(&g_rproc_info->rootpd_ready,
 						ATH12K_ROOTPD_READY_TIMEOUT);
 	if (!time_left) {
 		ath12k_err(ab, "RootPD ready wait timed out\n");
@@ -874,44 +963,74 @@ static int ath12k_ahb_boot_root_pd(struct ath12k_base *ab)
 
 static int ath12k_ahb_configure_rproc(struct ath12k_base *ab)
 {
-	struct ath12k_ahb *ab_ahb = ath12k_ab_to_ahb(ab);
 	int ret;
 
+	mutex_lock(&ath12k_rproc_info_lock);
+
 	ret = ath12k_ahb_get_rproc(ab);
-	if (ret < 0)
+	if (ret < 0) {
+		mutex_unlock(&ath12k_rproc_info_lock);
 		return ret;
+	}
 
-	ret = ath12k_ahb_register_rproc_notifier(ab);
+	ret = ath12k_ahb_register_rproc_notifier();
 	if (ret < 0) {
 		ret = dev_err_probe(&ab->pdev->dev, ret,
 				    "failed to register rproc notifier\n");
-		goto err_put_rproc;
+		goto err_cleanup_userpd;
 	}
 
-	if (ab_ahb->tgt_rproc->state != RPROC_RUNNING) {
+	if (g_rproc_info->tgt_rproc->state != RPROC_RUNNING) {
 		ret = ath12k_ahb_boot_root_pd(ab);
 		if (ret < 0) {
 			ath12k_err(ab, "failed to boot the remote processor Q6\n");
 			goto err_unreg_notifier;
 		}
+		g_rproc_info->rootpd_booted_by_driver = true;
 	}
 
-	return ath12k_ahb_config_rproc_irq(ab);
+	mutex_unlock(&ath12k_rproc_info_lock);
+	return 0;
 
 err_unreg_notifier:
-	ath12k_ahb_unregister_rproc_notifier(ab);
+	ath12k_ahb_unregister_rproc_notifier();
+
+err_cleanup_userpd:
+	ath12k_ahb_cleanup_userpd(ab);
+
+	if (g_rproc_info && !g_rproc_info->num_userpd) {
+		rproc_put(g_rproc_info->tgt_rproc);
+		kfree(g_rproc_info);
+		g_rproc_info = NULL;
+	}
 
-err_put_rproc:
-	rproc_put(ab_ahb->tgt_rproc);
+	mutex_unlock(&ath12k_rproc_info_lock);
 	return ret;
 }
 
 static void ath12k_ahb_deconfigure_rproc(struct ath12k_base *ab)
 {
 	struct ath12k_ahb *ab_ahb = ath12k_ab_to_ahb(ab);
+	struct ath12k_ahb_rproc_info *rproc_info = ab_ahb->rproc_info;
+
+	lockdep_assert_held(&ath12k_rproc_info_lock);
+
+	if (!rproc_info || !g_rproc_info)
+		return;
 
-	ath12k_ahb_unregister_rproc_notifier(ab);
-	rproc_put(ab_ahb->tgt_rproc);
+	ath12k_ahb_cleanup_userpd(ab);
+
+	if (!g_rproc_info->num_userpd) {
+		ath12k_ahb_unregister_rproc_notifier();
+
+		if (g_rproc_info->rootpd_booted_by_driver &&
+		    g_rproc_info->tgt_rproc->state == RPROC_RUNNING)
+			rproc_shutdown(g_rproc_info->tgt_rproc);
+
+		rproc_put(g_rproc_info->tgt_rproc);
+		kfree(g_rproc_info);
+		g_rproc_info = NULL;
+	}
 }
 
 static int ath12k_ahb_resource_init(struct ath12k_base *ab)
@@ -1094,6 +1213,10 @@ static int ath12k_ahb_probe(struct platform_device *pdev)
 	if (ret)
 		goto err_ce_free;
 
+	ret = ath12k_ahb_config_rproc_irq(ab);
+	if (ret)
+		goto err_rproc_deconfigure;
+
 	ret = ath12k_ahb_config_irq(ab);
 	if (ret) {
 		ath12k_err(ab, "failed to configure irq: %d\n", ret);
@@ -1121,7 +1244,9 @@ static int ath12k_ahb_probe(struct platform_device *pdev)
 	ab_ahb->device_family_ops->arch_deinit(ab);
 
 err_rproc_deconfigure:
+	mutex_lock(&ath12k_rproc_info_lock);
 	ath12k_ahb_deconfigure_rproc(ab);
+	mutex_unlock(&ath12k_rproc_info_lock);
 
 err_ce_free:
 	ath12k_ce_free_pipes(ab);
@@ -1163,7 +1288,9 @@ static void ath12k_ahb_free_resources(struct ath12k_base *ab)
 	ath12k_hal_srng_deinit(ab);
 	ath12k_ce_free_pipes(ab);
 	ath12k_ahb_resource_deinit(ab);
+	mutex_lock(&ath12k_rproc_info_lock);
 	ath12k_ahb_deconfigure_rproc(ab);
+	mutex_unlock(&ath12k_rproc_info_lock);
 	ab_ahb->device_family_ops->arch_deinit(ab);
 	ath12k_core_free(ab);
 	platform_set_drvdata(pdev, NULL);
diff --git a/drivers/net/wireless/ath/ath12k/ahb.h b/drivers/net/wireless/ath/ath12k/ahb.h
index 037347ccd21b..cdb58b07338f 100644
--- a/drivers/net/wireless/ath/ath12k/ahb.h
+++ b/drivers/net/wireless/ath/ath12k/ahb.h
@@ -69,13 +69,19 @@ struct ath12k_ahb_device_family_ops {
 	void (*arch_deinit)(struct ath12k_base *ab);
 };
 
-struct ath12k_ahb {
-	struct ath12k_base *ab;
+struct ath12k_ahb_rproc_info {
 	struct rproc *tgt_rproc;
-	struct clk *xo_clk;
-	struct completion rootpd_ready;
 	struct notifier_block root_pd_nb;
 	void *root_pd_notifier;
+	struct completion rootpd_ready;
+	u8 num_userpd;
+	bool rootpd_booted_by_driver;
+	struct ath12k_ahb *userpd[ATH12K_MAX_DEVICES];
+};
+
+struct ath12k_ahb {
+	struct ath12k_base *ab;
+	struct clk *xo_clk;
 	struct qcom_smem_state *spawn_state;
 	struct qcom_smem_state *stop_state;
 	struct completion userpd_spawned;
@@ -88,6 +94,7 @@ struct ath12k_ahb {
 	const struct ath12k_ahb_ops *ahb_ops;
 	const struct ath12k_ahb_device_family_ops *device_family_ops;
 	bool scm_auth_enabled;
+	struct ath12k_ahb_rproc_info *rproc_info;
 };
 
 struct ath12k_ahb_driver {
-- 
2.34.1




More information about the ath12k mailing list