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; - ab_ahb->root_pd_nb.notifier_call = ath12k_ahb_root_pd_state_notifier; - init_completion(&ab_ahb->rootpd_ready); + lockdep_assert_held(&ath12k_rproc_info_lock); - 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); + if (g_rproc_info->root_pd_notifier) + return 0; + + 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) { - struct ath12k_ahb *ab_ahb = ath12k_ab_to_ahb(ab); + lockdep_assert_held(&ath12k_rproc_info_lock); - if (!ab_ahb->root_pd_notifier) { - ath12k_err(ab, "Rproc notifier not registered\n"); + if (!g_rproc_info->root_pd_notifier) return; - } - qcom_unregister_ssr_notifier(ab_ahb->root_pd_notifier, - &ab_ahb->root_pd_nb); - ab_ahb->root_pd_notifier = NULL; + 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; + + lockdep_assert_held(&ath12k_rproc_info_lock); + + if (!rproc_info) + return; + + 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; - ret = ath12k_ahb_get_rproc(ab); - if (ret < 0) - return ret; + mutex_lock(&ath12k_rproc_info_lock); - ret = ath12k_ahb_register_rproc_notifier(ab); + ret = ath12k_ahb_get_rproc(ab); + if (ret < 0) { + mutex_unlock(&ath12k_rproc_info_lock); + return ret; + } + + 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_put_rproc: - rproc_put(ab_ahb->tgt_rproc); +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; + } + + 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; - ath12k_ahb_unregister_rproc_notifier(ab); - rproc_put(ab_ahb->tgt_rproc); + lockdep_assert_held(&ath12k_rproc_info_lock); + + if (!rproc_info || !g_rproc_info) + return; + + 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 {