Message ID | 1585244270-637-5-git-send-email-mkshah@codeaurora.org (mailing list archive) |
---|---|
State | Superseded |
Headers | show |
Series | Invoke rpmh_flush for non OSI targets | expand |
Hi, On Thu, Mar 26, 2020 at 10:38 AM Maulik Shah <mkshah@codeaurora.org> wrote: > > Add changes to invoke rpmh flush() from cpu pm notification. > This is done when last cpu is entering power collapse and s/when last/when the last/ > controller is not busy. A few overall comments: * CPU_PM certainly seems interesting and I wasn't aware of it. * Your series is predicated on nobody calling any of the RPMH functions after the last CPU enters low power mode with CPU_PM_ENTER and before some CPU calls CPU_PM_EXIT. Is it worth adding a check for that to the code? This is super important for the 'zero-active-TCS' case but even for the 'some-active-TCS' it's still important that nobody tries to update the sleep/wake values after you flush. * I think your series is focused on idle, right? What about suspend/resume? You need a flush before suspend, right? Do you need to register for something to handle suspend, or do the pm callbacks all get called for full system suspend? If so, how late do they get called? > Controller that do have 'HW solver' mode do not need to register s/Controller/Controllers > @@ -85,22 +85,30 @@ struct rpmh_ctrlr { > * Resource State Coordinator controller (RSC) > * > * @name: controller identifier > + * @base: start address of the RSC's DRV registers > * @tcs_base: start address of the TCS registers in this controller > * @id: instance id in the controller (Direct Resource Voter) > * @num_tcs: number of TCSes in this DRV > + * @rsc_pm: CPU PM notifier for controller > + * @cpus_in_pc: CPU mask for cpus in idle power collapse Name "cpus_entered_pm" to match Linux naming? Also the comment should clearly say that this is only used in non-solver mode. > * @tcs: TCS groups > * @tcs_in_use: s/w state of the TCS > * @lock: synchronize state of the controller > + * @pm_lock: synchronize during PM notifications Comment should say that this is only for non-solver mode. > @@ -521,6 +527,71 @@ int rpmh_rsc_write_ctrl_data(struct rsc_drv *drv, const struct tcs_request *msg) > return tcs_ctrl_write(drv, msg); > } > > +/** > + * rpmh_rsc_ctrlr_is_busy: Check if any of the AMCs are busy. > + * > + * @drv: The controller > + * > + * Returns True if the TCSes are engaged in handling requests, False otherwise. This is almost, but not quite in kernel-doc syntax. Why not make it match the example in "Documentation/doc-guide/kernel-doc.rst"? Specifically trying to follow that style: * rpmh_rsc_ctrlr_is_busy() - Check if any of the AMCs are busy. * @drv: The controller * * Return: true if TCSes are engaged in handling requests; else false > +static bool rpmh_rsc_ctrlr_is_busy(struct rsc_drv *drv) > +{ > + int m; > + struct tcs_group *tcs = get_tcs_of_type(drv, ACTIVE_TCS); > + > + /** nit: why double star in "/**"? That's for kerneldoc comments. I don't think it makes sense here... > + * If we made an active request on a RSC that does not have a > + * dedicated TCS for active state use, then re-purposed wake TCSes > + * should be checked for not busy > + */ > + if (!tcs->num_tcs) > + tcs = get_tcs_of_type(drv, WAKE_TCS); > + > + for (m = tcs->offset; m < tcs->offset + tcs->num_tcs; m++) { > + if (!tcs_is_free(drv, m)) As per my documentation patch, tcs_is_free(): * Must be called with the drv->lock held or the tcs_lock for the TCS being * tested. ...I think you're OK calling without the lock since you're running on the last CPU (and you're blocking any other CPUs from coming online) and interrupts are disabled, but please document. > +static int rpmh_rsc_cpu_pm_callback(struct notifier_block *nfb, > + unsigned long action, void *v) > +{ > + struct rsc_drv *drv = container_of(nfb, struct rsc_drv, rsc_pm); > + unsigned long flags; > + int ret = NOTIFY_OK; > + > + spin_lock_irqsave(&drv->pm_lock, flags); nit: you can get away with just spin_lock(). cpu_pm_enter() is documented to be called with interrupts already disabled so the "irqsave" and "irqrestore" is useless. > + switch (action) { > + case CPU_PM_ENTER: > + cpumask_set_cpu(raw_smp_processor_id(), &drv->cpus_in_pc); > + > + if (!cpumask_equal(&drv->cpus_in_pc, cpu_online_mask)) Is it ever possible to offline a CPU after doing "CPU_PM_ENTER" on it? If so you'll never be "equal" to the online mask again. This seems safer: !cpumask_subset(cpu_online_mask, &drv->cpus_in_pc) > + goto exit; > + break; > + case CPU_PM_ENTER_FAILED: > + case CPU_PM_EXIT: > + cpumask_clear_cpu(raw_smp_processor_id(), &drv->cpus_in_pc); > + goto exit; > + } > + > + ret = rpmh_rsc_ctrlr_is_busy(drv); > + if (ret) { > + ret = NOTIFY_BAD; > + goto exit; > + } Are you sure you can't just skip the call to rpmh_rsc_ctrlr_is_busy()? Specifically, I'm not sure why you need to block until the last active mode transaction is done. a) If the active mode transaction was on its own TCS, I'd hope that the RPMH hardware would know to let the active transitions happen before triggering the sleep (please confirm) b) If the active mode transaction was borrowing the wake TCS the flush should notice and return NOTIFY_BAD anyway. Oh! ...but looking closer at case b) points out a problem anyway. You need to go into rpmh_flush() and stop having it loop on 'while (ret == -EAGAIN)". As per my documentation series (please review) when -EAGAIN is returned it's important that the caller enable interrupts for a little while before trying again. You're not doing that here. Maybe if you fix it then you can get rid of your special case rpmh_rsc_ctrlr_is_busy()? > + ret = rpmh_flush(&drv->client); Back when RPMH first landed we had a whole argument about whether it should be one file or two. Given that it's currently two files, I think the abstraction is that rpmh-rsc generally doesn't call into rpmh except for the very special rpmh_tx_done(). ...so seems like the whole pm_callback should move into rpmh.c and not in rpmh-rsc.c? > @@ -533,21 +604,20 @@ static int rpmh_probe_tcs_config(struct platform_device *pdev, > int i, ret, n, st = 0; > struct tcs_group *tcs; > struct resource *res; > - void __iomem *base; > char drv_id[10] = {0}; > > snprintf(drv_id, ARRAY_SIZE(drv_id), "drv-%d", drv->id); > res = platform_get_resource_byname(pdev, IORESOURCE_MEM, drv_id); > - base = devm_ioremap_resource(&pdev->dev, res); > - if (IS_ERR(base)) > - return PTR_ERR(base); > + drv->base = devm_ioremap_resource(&pdev->dev, res); > + if (IS_ERR(drv->base)) > + return PTR_ERR(drv->base); I don't understand this part of the change. Why move "base" into the driver data. It's not needed after probe. Please undo this. If you really feel like base needs to be separate please move to its own patch. > @@ -663,6 +734,20 @@ static int rpmh_rsc_probe(struct platform_device *pdev) > if (ret) > return ret; > > + /* > + * CPU PM notification are not required for controllers that support > + * 'HW solver' mode where they can be in autonomous mode executing low > + * power mode to power down. > + */ > + solver_config = readl_relaxed(drv->base + DRV_SOLVER_CONFIG); > + solver_config &= DRV_HW_SOLVER_MASK << DRV_HW_SOLVER_SHIFT; > + solver_config = solver_config >> DRV_HW_SOLVER_SHIFT; > + if (!solver_config) { > + drv->rsc_pm.notifier_call = rpmh_rsc_cpu_pm_callback; > + cpu_pm_register_notifier(&drv->rsc_pm); > + spin_lock_init(&drv->pm_lock); nit: init the spin lock before registering instead of after. > * @ctrlr: controller making request to flush cached data > * > - * Return: -EBUSY if the controller is busy, probably waiting on a response > - * to a RPMH request sent earlier. > + * Return: 0 on success, error number otherwise. > * > - * This function is always called from the sleep code from the last CPU > - * that is powering down the entire system. Since no other RPMH API would be > - * executing at this time, it is safe to run lockless. > + * This function can either be called from sleep code on the last CPU > + * (thus no spinlock needed) or with the ctrlr->cache_lock already held. Now you can remove the "or with the ctrlr->cache_lock already held" since it's no longer true. -Doug
Hi, On Fri, Mar 27, 2020 at 4:00 AM Maulik Shah <mkshah@codeaurora.org> wrote: > > * @ctrlr: controller making request to flush cached data > * > - * Return: -EBUSY if the controller is busy, probably waiting on a response > - * to a RPMH request sent earlier. > + * Return: 0 on success, error number otherwise. > * > - * This function is always called from the sleep code from the last CPU > - * that is powering down the entire system. Since no other RPMH API would be > - * executing at this time, it is safe to run lockless. > + * This function can either be called from sleep code on the last CPU > + * (thus no spinlock needed) or with the ctrlr->cache_lock already held. > > Now you can remove the "or with the ctrlr->cache_lock already held" > since it's no longer true. > > It can be true for other RSCs, so i kept as it is. I don't really understand this. The cache_lock is only a concept in "rpmh.c". How could another RSC grab the cache lock? If nothing else, can you remove this comment until support for those other RSCs are added and we can evaluate then? -Doug
Hi, On 3/27/2020 11:52 PM, Doug Anderson wrote: > Hi, > > On Fri, Mar 27, 2020 at 4:00 AM Maulik Shah <mkshah@codeaurora.org> wrote: >> * @ctrlr: controller making request to flush cached data >> * >> - * Return: -EBUSY if the controller is busy, probably waiting on a response >> - * to a RPMH request sent earlier. >> + * Return: 0 on success, error number otherwise. >> * >> - * This function is always called from the sleep code from the last CPU >> - * that is powering down the entire system. Since no other RPMH API would be >> - * executing at this time, it is safe to run lockless. >> + * This function can either be called from sleep code on the last CPU >> + * (thus no spinlock needed) or with the ctrlr->cache_lock already held. >> >> Now you can remove the "or with the ctrlr->cache_lock already held" >> since it's no longer true. >> >> It can be true for other RSCs, so i kept as it is. > I don't really understand this. The cache_lock is only a concept in > "rpmh.c". How could another RSC grab the cache lock? If nothing > else, can you remove this comment until support for those other RSCs > are added and we can evaluate then? > > -Doug Okay i will remove this comment until support for other RSCs are added. Thanks, Maulik
diff --git a/drivers/soc/qcom/rpmh-internal.h b/drivers/soc/qcom/rpmh-internal.h index 6eec32b..33ee50b 100644 --- a/drivers/soc/qcom/rpmh-internal.h +++ b/drivers/soc/qcom/rpmh-internal.h @@ -85,22 +85,30 @@ struct rpmh_ctrlr { * Resource State Coordinator controller (RSC) * * @name: controller identifier + * @base: start address of the RSC's DRV registers * @tcs_base: start address of the TCS registers in this controller * @id: instance id in the controller (Direct Resource Voter) * @num_tcs: number of TCSes in this DRV + * @rsc_pm: CPU PM notifier for controller + * @cpus_in_pc: CPU mask for cpus in idle power collapse * @tcs: TCS groups * @tcs_in_use: s/w state of the TCS * @lock: synchronize state of the controller + * @pm_lock: synchronize during PM notifications * @client: handle to the DRV's client. */ struct rsc_drv { const char *name; + void __iomem *base; void __iomem *tcs_base; int id; int num_tcs; + struct notifier_block rsc_pm; + struct cpumask cpus_in_pc; struct tcs_group tcs[TCS_TYPE_NR]; DECLARE_BITMAP(tcs_in_use, MAX_TCS_NR); spinlock_t lock; + spinlock_t pm_lock; struct rpmh_ctrlr client; }; diff --git a/drivers/soc/qcom/rpmh-rsc.c b/drivers/soc/qcom/rpmh-rsc.c index e278fc1..ee052cc 100644 --- a/drivers/soc/qcom/rpmh-rsc.c +++ b/drivers/soc/qcom/rpmh-rsc.c @@ -6,6 +6,7 @@ #define pr_fmt(fmt) "%s " fmt, KBUILD_MODNAME #include <linux/atomic.h> +#include <linux/cpu_pm.h> #include <linux/delay.h> #include <linux/interrupt.h> #include <linux/io.h> @@ -30,7 +31,12 @@ #define RSC_DRV_TCS_OFFSET 672 #define RSC_DRV_CMD_OFFSET 20 -/* DRV Configuration Information Register */ +/* DRV HW Solver Configuration Information Register */ +#define DRV_SOLVER_CONFIG 0x04 +#define DRV_HW_SOLVER_MASK 1 +#define DRV_HW_SOLVER_SHIFT 24 + +/* DRV TCS Configuration Information Register */ #define DRV_PRNT_CHLD_CONFIG 0x0C #define DRV_NUM_TCS_MASK 0x3F #define DRV_NUM_TCS_SHIFT 6 @@ -521,6 +527,71 @@ int rpmh_rsc_write_ctrl_data(struct rsc_drv *drv, const struct tcs_request *msg) return tcs_ctrl_write(drv, msg); } +/** + * rpmh_rsc_ctrlr_is_busy: Check if any of the AMCs are busy. + * + * @drv: The controller + * + * Returns True if the TCSes are engaged in handling requests, False otherwise. + */ +static bool rpmh_rsc_ctrlr_is_busy(struct rsc_drv *drv) +{ + int m; + struct tcs_group *tcs = get_tcs_of_type(drv, ACTIVE_TCS); + + /** + * If we made an active request on a RSC that does not have a + * dedicated TCS for active state use, then re-purposed wake TCSes + * should be checked for not busy + */ + if (!tcs->num_tcs) + tcs = get_tcs_of_type(drv, WAKE_TCS); + + for (m = tcs->offset; m < tcs->offset + tcs->num_tcs; m++) { + if (!tcs_is_free(drv, m)) + return true; + } + + return false; +} + +static int rpmh_rsc_cpu_pm_callback(struct notifier_block *nfb, + unsigned long action, void *v) +{ + struct rsc_drv *drv = container_of(nfb, struct rsc_drv, rsc_pm); + unsigned long flags; + int ret = NOTIFY_OK; + + spin_lock_irqsave(&drv->pm_lock, flags); + + switch (action) { + case CPU_PM_ENTER: + cpumask_set_cpu(raw_smp_processor_id(), &drv->cpus_in_pc); + + if (!cpumask_equal(&drv->cpus_in_pc, cpu_online_mask)) + goto exit; + break; + case CPU_PM_ENTER_FAILED: + case CPU_PM_EXIT: + cpumask_clear_cpu(raw_smp_processor_id(), &drv->cpus_in_pc); + goto exit; + } + + ret = rpmh_rsc_ctrlr_is_busy(drv); + if (ret) { + ret = NOTIFY_BAD; + goto exit; + } + + ret = rpmh_flush(&drv->client); + if (ret) + ret = NOTIFY_BAD; + +exit: + spin_unlock_irqrestore(&drv->pm_lock, flags); + return ret; +} + static int rpmh_probe_tcs_config(struct platform_device *pdev, struct rsc_drv *drv) { @@ -533,21 +604,20 @@ static int rpmh_probe_tcs_config(struct platform_device *pdev, int i, ret, n, st = 0; struct tcs_group *tcs; struct resource *res; - void __iomem *base; char drv_id[10] = {0}; snprintf(drv_id, ARRAY_SIZE(drv_id), "drv-%d", drv->id); res = platform_get_resource_byname(pdev, IORESOURCE_MEM, drv_id); - base = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(base)) - return PTR_ERR(base); + drv->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(drv->base)) + return PTR_ERR(drv->base); ret = of_property_read_u32(dn, "qcom,tcs-offset", &offset); if (ret) return ret; - drv->tcs_base = base + offset; + drv->tcs_base = drv->base + offset; - config = readl_relaxed(base + DRV_PRNT_CHLD_CONFIG); + config = readl_relaxed(drv->base + DRV_PRNT_CHLD_CONFIG); max_tcs = config; max_tcs &= DRV_NUM_TCS_MASK << (DRV_NUM_TCS_SHIFT * drv->id); @@ -621,6 +691,7 @@ static int rpmh_rsc_probe(struct platform_device *pdev) struct device_node *dn = pdev->dev.of_node; struct rsc_drv *drv; int ret, irq; + u32 solver_config; /* * Even though RPMh doesn't directly use cmd-db, all of its children @@ -663,6 +734,20 @@ static int rpmh_rsc_probe(struct platform_device *pdev) if (ret) return ret; + /* + * CPU PM notification are not required for controllers that support + * 'HW solver' mode where they can be in autonomous mode executing low + * power mode to power down. + */ + solver_config = readl_relaxed(drv->base + DRV_SOLVER_CONFIG); + solver_config &= DRV_HW_SOLVER_MASK << DRV_HW_SOLVER_SHIFT; + solver_config = solver_config >> DRV_HW_SOLVER_SHIFT; + if (!solver_config) { + drv->rsc_pm.notifier_call = rpmh_rsc_cpu_pm_callback; + cpu_pm_register_notifier(&drv->rsc_pm); + spin_lock_init(&drv->pm_lock); + } + /* Enable the active TCS to send requests immediately */ write_tcs_reg(drv, RSC_DRV_IRQ_ENABLE, 0, drv->tcs[ACTIVE_TCS].mask); diff --git a/drivers/soc/qcom/rpmh.c b/drivers/soc/qcom/rpmh.c index 5147d82..565b6e4 100644 --- a/drivers/soc/qcom/rpmh.c +++ b/drivers/soc/qcom/rpmh.c @@ -297,12 +297,10 @@ static int flush_batch(struct rpmh_ctrlr *ctrlr) { struct batch_cache_req *req; const struct rpmh_request *rpm_msg; - unsigned long flags; int ret = 0; int i; /* Send Sleep/Wake requests to the controller, expect no response */ - spin_lock_irqsave(&ctrlr->cache_lock, flags); list_for_each_entry(req, &ctrlr->batch_cache, list) { for (i = 0; i < req->count; i++) { rpm_msg = req->rpm_msgs + i; @@ -312,7 +310,6 @@ static int flush_batch(struct rpmh_ctrlr *ctrlr) break; } } - spin_unlock_irqrestore(&ctrlr->cache_lock, flags); return ret; } @@ -437,12 +434,10 @@ static int send_single(struct rpmh_ctrlr *ctrlr, enum rpmh_state state, * * @ctrlr: controller making request to flush cached data * - * Return: -EBUSY if the controller is busy, probably waiting on a response - * to a RPMH request sent earlier. + * Return: 0 on success, error number otherwise. * - * This function is always called from the sleep code from the last CPU - * that is powering down the entire system. Since no other RPMH API would be - * executing at this time, it is safe to run lockless. + * This function can either be called from sleep code on the last CPU + * (thus no spinlock needed) or with the ctrlr->cache_lock already held. */ int rpmh_flush(struct rpmh_ctrlr *ctrlr) { @@ -466,10 +461,6 @@ int rpmh_flush(struct rpmh_ctrlr *ctrlr) if (ret) return ret; - /* - * Nobody else should be calling this function other than system PM, - * hence we can run without locks. - */ list_for_each_entry(p, &ctrlr->cache, list) { if (!is_req_valid(p)) { pr_debug("%s: skipping RPMH req: a:%#x s:%#x w:%#x",
Add changes to invoke rpmh flush() from cpu pm notification. This is done when last cpu is entering power collapse and controller is not busy. Controller that do have 'HW solver' mode do not need to register for cpu pm notification. They may be in autonomous mode executing low power mode do not require rpmh_flush() to happen from CPU PM notification. Signed-off-by: Maulik Shah <mkshah@codeaurora.org> --- drivers/soc/qcom/rpmh-internal.h | 8 ++++ drivers/soc/qcom/rpmh-rsc.c | 99 +++++++++++++++++++++++++++++++++++++--- drivers/soc/qcom/rpmh.c | 15 ++---- 3 files changed, 103 insertions(+), 19 deletions(-)