diff mbox series

[v13,4/5] soc: qcom: rpmh: Invoke rpmh_flush() for dirty caches

Message ID 1583746236-13325-5-git-send-email-mkshah@codeaurora.org (mailing list archive)
State Superseded
Headers show
Series Invoke rpmh_flush for non OSI targets | expand

Commit Message

Maulik Shah March 9, 2020, 9:30 a.m. UTC
Add changes to invoke rpmh flush() from within cache_lock when the data in
cache is dirty.

Introduce two new APIs for this. Clients can use rpmh_start_transaction()
before any rpmh transaction once done invoke rpmh_end_transaction() which
internally invokes rpmh_flush() if the caches has become dirty.

Add support to control this with flush_dirty flag.

Signed-off-by: Maulik Shah <mkshah@codeaurora.org>
Reviewed-by: Srinivas Rao L <lsrao@codeaurora.org>
---
 drivers/soc/qcom/rpmh-internal.h |  4 +++
 drivers/soc/qcom/rpmh-rsc.c      |  6 +++-
 drivers/soc/qcom/rpmh.c          | 64 ++++++++++++++++++++++++++++++++--------
 include/soc/qcom/rpmh.h          | 10 +++++++
 4 files changed, 71 insertions(+), 13 deletions(-)

Comments

Doug Anderson March 9, 2020, 11:43 p.m. UTC | #1
Hi,

On Mon, Mar 9, 2020 at 2:31 AM Maulik Shah <mkshah@codeaurora.org> wrote:
>
> Add changes to invoke rpmh flush() from within cache_lock when the data in
> cache is dirty.
>
> Introduce two new APIs for this. Clients can use rpmh_start_transaction()
> before any rpmh transaction once done invoke rpmh_end_transaction() which
> internally invokes rpmh_flush() if the caches has become dirty.
>
> Add support to control this with flush_dirty flag.
>
> Signed-off-by: Maulik Shah <mkshah@codeaurora.org>
> Reviewed-by: Srinivas Rao L <lsrao@codeaurora.org>
> ---
>  drivers/soc/qcom/rpmh-internal.h |  4 +++
>  drivers/soc/qcom/rpmh-rsc.c      |  6 +++-
>  drivers/soc/qcom/rpmh.c          | 64 ++++++++++++++++++++++++++++++++--------
>  include/soc/qcom/rpmh.h          | 10 +++++++
>  4 files changed, 71 insertions(+), 13 deletions(-)

As mentioned previously but not addressed [3], I believe your series
breaks things if there are zero ACTIVE TCSs and you're using the
immediate-flush solution.  Specifically any attempt to set something's
"active" state will clobber the sleep/wake.  I believe this is hard to
fix, especially if you want rpmh_write_async() to work properly and
need to be robust to the last man going down while rpmh_write_async()
is running but hasn't finished.  My suggestion was to consider it to
be an error at probe time for now.

Actually, though, I'd be super surprised if the "active == 0" case
works anyway.  Aside from subtle problems of not handling -EAGAIN (see
another previous message that you didn't respond to [2]), I think
you'll also get failures because you never enable interrupts in
RSC_DRV_IRQ_ENABLE for anything other than the ACTIVE_TCS.  Thus
you'll never get interrupts saying when your transactions on the
borrowed "wake" TCS finish.

Speaking of previous emails that you didn't respond to, I think you
still have these action items:

* Document that rpmh_write(active) and rpmh_write_async(active) also
updates wake state. [1]

* Change is_req_valid() to still return true if (sleep == wake), or
keep track of "active" and return true if (sleep != wake || wake !=
active). [1]

* Document that for batch a write to active doesn't update wake. [1]


> diff --git a/drivers/soc/qcom/rpmh-internal.h b/drivers/soc/qcom/rpmh-internal.h
> index 6eec32b..d36be3d 100644
> --- a/drivers/soc/qcom/rpmh-internal.h
> +++ b/drivers/soc/qcom/rpmh-internal.h
> @@ -70,13 +70,17 @@ struct rpmh_request {
>   *
>   * @cache: the list of cached requests
>   * @cache_lock: synchronize access to the cache data
> + * @active_clients: count of rpmh transaction in progress
>   * @dirty: was the cache updated since flush
> + * @flush_dirty: if the dirty cache need immediate flush
>   * @batch_cache: Cache sleep and wake requests sent as batch
>   */
>  struct rpmh_ctrlr {
>         struct list_head cache;
>         spinlock_t cache_lock;
> +       u32 active_clients;
>         bool dirty;
> +       bool flush_dirty;
>         struct list_head batch_cache;
>  };
>
> diff --git a/drivers/soc/qcom/rpmh-rsc.c b/drivers/soc/qcom/rpmh-rsc.c
> index e278fc1..b6391e1 100644
> --- a/drivers/soc/qcom/rpmh-rsc.c
> +++ b/drivers/soc/qcom/rpmh-rsc.c
> @@ -61,6 +61,8 @@
>  #define CMD_STATUS_ISSUED              BIT(8)
>  #define CMD_STATUS_COMPL               BIT(16)
>
> +#define FLUSH_DIRTY                    1
> +
>  static u32 read_tcs_reg(struct rsc_drv *drv, int reg, int tcs_id, int cmd_id)
>  {
>         return readl_relaxed(drv->tcs_base + reg + RSC_DRV_TCS_OFFSET * tcs_id +
> @@ -670,13 +672,15 @@ static int rpmh_rsc_probe(struct platform_device *pdev)
>         INIT_LIST_HEAD(&drv->client.cache);
>         INIT_LIST_HEAD(&drv->client.batch_cache);
>
> +       drv->client.flush_dirty = device_get_match_data(&pdev->dev);
> +
>         dev_set_drvdata(&pdev->dev, drv);
>
>         return devm_of_platform_populate(&pdev->dev);
>  }
>
>  static const struct of_device_id rpmh_drv_match[] = {
> -       { .compatible = "qcom,rpmh-rsc", },
> +       { .compatible = "qcom,rpmh-rsc", .data = (void *)FLUSH_DIRTY },

Ick.  This is just confusing.  IMO better to set
'drv->client.flush_dirty = true' directly in probe with a comment
saying that it could be removed if we had OSI.

...and while you're at it, why not fire off a separate patch (not in
your series) adding the stub to 'include/linux/psci.h'.  Then when we
revisit this in a year it'll be there and it'll be super easy to set
the value properly.


>         { }
>  };
>
> diff --git a/drivers/soc/qcom/rpmh.c b/drivers/soc/qcom/rpmh.c
> index 5bed8f4..9d40209 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;
>  }
> @@ -433,16 +430,63 @@ static int send_single(struct rpmh_ctrlr *ctrlr, enum rpmh_state state,
>  }
>
>  /**
> + * rpmh_start_transaction: Indicates start of rpmh transactions, this
> + * must be ended by invoking rpmh_end_transaction().
> + *
> + * @dev: the device making the request
> + */
> +void rpmh_start_transaction(const struct device *dev)
> +{
> +       struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr(dev);
> +       unsigned long flags;
> +
> +       if (!ctrlr->flush_dirty)
> +               return;
> +
> +       spin_lock_irqsave(&ctrlr->cache_lock, flags);
> +       ctrlr->active_clients++;

Wouldn't hurt to have something like:

/*
 * Detect likely leak; we shouldn't have 1000
 * people making in-flight changes at the same time.
 */
WARN_ON(ctrlr->active_clients > 1000)


> +       spin_unlock_irqrestore(&ctrlr->cache_lock, flags);
> +}
> +EXPORT_SYMBOL(rpmh_start_transaction);
> +
> +/**
> + * rpmh_end_transaction: Indicates end of rpmh transactions. All dirty data
> + * in cache can be flushed immediately when ctrlr->flush_dirty is set
> + *
> + * @dev: the device making the request
> + *
> + * Return: 0 on success, error number otherwise.
> + */
> +int rpmh_end_transaction(const struct device *dev)
> +{
> +       struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr(dev);
> +       unsigned long flags;
> +       int ret = 0;
> +
> +       if (!ctrlr->flush_dirty)
> +               return ret;
> +
> +       spin_lock_irqsave(&ctrlr->cache_lock, flags);

WARN_ON(!active_clients);


> +
> +       ctrlr->active_clients--;
> +       if (ctrlr->dirty && !ctrlr->active_clients)
> +               ret = rpmh_flush(ctrlr);

As mentioned previously [2], I don't think it's valid to call
rpmh_flush() with interrupts disabled.  Specifically (as of your
previous patch) rpmh_flush now loops if rpmh_rsc_invalidate() returns
-EAGAIN.  I believe that the caller needs to enable interrupts for a
little bit before trying again.  If the caller doesn't need to enable
interrupts for a little bit before trying again then why was -EAGAIN
even returned?  tcs_invalidate() could have just looped itself and all
the code would be much simpler.


> +
> +       spin_unlock_irqrestore(&ctrlr->cache_lock, flags);
> +
> +       return ret;
> +}
> +EXPORT_SYMBOL(rpmh_end_transaction);
> +
> +/**
>   * rpmh_flush: Flushes the buffered active and sleep sets to TCS
>   *
>   * @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)
>  {
> @@ -464,10 +508,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",
> diff --git a/include/soc/qcom/rpmh.h b/include/soc/qcom/rpmh.h
> index f9ec353..85e1ab2 100644
> --- a/include/soc/qcom/rpmh.h
> +++ b/include/soc/qcom/rpmh.h
> @@ -22,6 +22,10 @@ int rpmh_write_batch(const struct device *dev, enum rpmh_state state,
>
>  int rpmh_invalidate(const struct device *dev);
>
> +void rpmh_start_transaction(const struct device *dev);
> +
> +int rpmh_end_transaction(const struct device *dev);
> +
>  #else
>
>  static inline int rpmh_write(const struct device *dev, enum rpmh_state state,
> @@ -41,6 +45,12 @@ static inline int rpmh_write_batch(const struct device *dev,
>  static inline int rpmh_invalidate(const struct device *dev)
>  { return -ENODEV; }
>
> +void rpmh_start_transaction(const struct device *dev)
> +{ return -ENODEV; }

Unexpected return from void function.


> +
> +int rpmh_end_transaction(const struct device *dev)
> +{ return -ENODEV; }
> +
>  #endif /* CONFIG_QCOM_RPMH */
>
>  #endif /* __SOC_QCOM_RPMH_H__ */

[1] https://lore.kernel.org/r/CAD=FV=VzNnRdDN5uPYskJ6kQHq2bAi2ysEqt0=taagdd_qZb-g@mail.gmail.com
[2] https://lore.kernel.org/r/CAD=FV=UYpO2rSOoF-OdZd3jKfSZGKnpQJPoiE5fzH+u1uafS6g@mail.gmail.com
[3] https://lore.kernel.org/r/CAD=FV=VNaqwiti+UB8fLgjF5r2CD2xeF_p7qHS-_yXqf+ZDrBg@mail.gmail.com



-Doug
Maulik Shah March 10, 2020, 11:19 a.m. UTC | #2
On 3/10/2020 5:13 AM, Doug Anderson wrote:
> Hi,
>
> On Mon, Mar 9, 2020 at 2:31 AM Maulik Shah <mkshah@codeaurora.org> wrote:
>> Add changes to invoke rpmh flush() from within cache_lock when the data in
>> cache is dirty.
>>
>> Introduce two new APIs for this. Clients can use rpmh_start_transaction()
>> before any rpmh transaction once done invoke rpmh_end_transaction() which
>> internally invokes rpmh_flush() if the caches has become dirty.
>>
>> Add support to control this with flush_dirty flag.
>>
>> Signed-off-by: Maulik Shah <mkshah@codeaurora.org>
>> Reviewed-by: Srinivas Rao L <lsrao@codeaurora.org>
>> ---
>>  drivers/soc/qcom/rpmh-internal.h |  4 +++
>>  drivers/soc/qcom/rpmh-rsc.c      |  6 +++-
>>  drivers/soc/qcom/rpmh.c          | 64 ++++++++++++++++++++++++++++++++--------
>>  include/soc/qcom/rpmh.h          | 10 +++++++
>>  4 files changed, 71 insertions(+), 13 deletions(-)
> As mentioned previously but not addressed [3], I believe your series
> breaks things if there are zero ACTIVE TCSs and you're using the
> immediate-flush solution.  Specifically any attempt to set something's
> "active" state will clobber the sleep/wake.  I believe this is hard to
> fix, especially if you want rpmh_write_async() to work properly and
> need to be robust to the last man going down while rpmh_write_async()
> is running but hasn't finished.  My suggestion was to consider it to
> be an error at probe time for now.
>
> Actually, though, I'd be super surprised if the "active == 0" case
> works anyway.  Aside from subtle problems of not handling -EAGAIN (see
> another previous message that you didn't respond to [2]), I think
> you'll also get failures because you never enable interrupts in
> RSC_DRV_IRQ_ENABLE for anything other than the ACTIVE_TCS.  Thus
> you'll never get interrupts saying when your transactions on the
> borrowed "wake" TCS finish.

No, it shouldn’t effect even with "non-OSI-mode + 0 ACTIVE TCS"

i just replied on v9, pasting same on v13 as well.

After taking your suggestion to do rpmh start/end transaction() in v13, rpmh_end_transaction()
invokes rpmh_flush() only for the last client and by this time expecting all of rpmh_write()
and rpmh_write_batch() will be already “finished” as client first waits for them to finish
and then only invokes end.

So driver is good to handle rpmh_write() and rpmh_write_batch() calls.

Regarding rpmh_write_async() call, which is a fire-n-forget request from SW and client driver
may immediately invoke rpmh_end_transaction() after this.

this case is also handled properly…
Lets again take an example for understanding this..

1.    Client invokes rpmh_write_async() to send ACTIVE cmds for targets which has zero ACTIVE TCS

    Rpmh driver Re-purposes one of SLEEP/WAKE TCS to use as ACTIVE, internally this also sets
    drv->tcs_in_use to true for respective SLEEP/WAKE TCS.

2.    Client now without waiting for above to finish, goes ahead and invokes rpmh_end_transaction()
    which calls rpmh_flush() (in case cache become dirty)

    Now if re-purposed TCS is still in use in HW (transaction in progress), we still have
    drv->tcs_in_use set. So the rpmh_rsc_invalidate() (invoked from rpmh_flush()) will keep on
    returning -EAGAIN until that TCS becomes free to use and then goes ahead to finish its job.  


...i will add "suggested-by" you in next revision.


> Speaking of previous emails that you didn't respond to, I think you
> still have these action items:
>
> * Document that rpmh_write(active) and rpmh_write_async(active) also
> updates wake state. [1]
I will update in v14.
>
> * Change is_req_valid() to still return true if (sleep == wake), or
> keep track of "active" and return true if (sleep != wake || wake !=
> active). [1]
Not required, as replied in v10 now only.
> * Document that for batch a write to active doesn't update wake. [1]
I will update in v14.
>
>> diff --git a/drivers/soc/qcom/rpmh-internal.h b/drivers/soc/qcom/rpmh-internal.h
>> index 6eec32b..d36be3d 100644
>> --- a/drivers/soc/qcom/rpmh-internal.h
>> +++ b/drivers/soc/qcom/rpmh-internal.h
>> @@ -70,13 +70,17 @@ struct rpmh_request {
>>   *
>>   * @cache: the list of cached requests
>>   * @cache_lock: synchronize access to the cache data
>> + * @active_clients: count of rpmh transaction in progress
>>   * @dirty: was the cache updated since flush
>> + * @flush_dirty: if the dirty cache need immediate flush
>>   * @batch_cache: Cache sleep and wake requests sent as batch
>>   */
>>  struct rpmh_ctrlr {
>>         struct list_head cache;
>>         spinlock_t cache_lock;
>> +       u32 active_clients;
>>         bool dirty;
>> +       bool flush_dirty;
>>         struct list_head batch_cache;
>>  };
>>
>> diff --git a/drivers/soc/qcom/rpmh-rsc.c b/drivers/soc/qcom/rpmh-rsc.c
>> index e278fc1..b6391e1 100644
>> --- a/drivers/soc/qcom/rpmh-rsc.c
>> +++ b/drivers/soc/qcom/rpmh-rsc.c
>> @@ -61,6 +61,8 @@
>>  #define CMD_STATUS_ISSUED              BIT(8)
>>  #define CMD_STATUS_COMPL               BIT(16)
>>
>> +#define FLUSH_DIRTY                    1
>> +
>>  static u32 read_tcs_reg(struct rsc_drv *drv, int reg, int tcs_id, int cmd_id)
>>  {
>>         return readl_relaxed(drv->tcs_base + reg + RSC_DRV_TCS_OFFSET * tcs_id +
>> @@ -670,13 +672,15 @@ static int rpmh_rsc_probe(struct platform_device *pdev)
>>         INIT_LIST_HEAD(&drv->client.cache);
>>         INIT_LIST_HEAD(&drv->client.batch_cache);
>>
>> +       drv->client.flush_dirty = device_get_match_data(&pdev->dev);
>> +
>>         dev_set_drvdata(&pdev->dev, drv);
>>
>>         return devm_of_platform_populate(&pdev->dev);
>>  }
>>
>>  static const struct of_device_id rpmh_drv_match[] = {
>> -       { .compatible = "qcom,rpmh-rsc", },
>> +       { .compatible = "qcom,rpmh-rsc", .data = (void *)FLUSH_DIRTY },
> Ick.  This is just confusing.  IMO better to set
> 'drv->client.flush_dirty = true' directly in probe with a comment
> saying that it could be removed if we had OSI.
Done.
i will keep this bit earlier in probe with commet, so later if we detect rsc to be in hierarchy
from [1], we can override this to be 0 within rpmh_probe_power_domain().

[1] https://patchwork.kernel.org/patch/11391229/

>
> ...and while you're at it, why not fire off a separate patch (not in
> your series) adding the stub to 'include/linux/psci.h'.  Then when we
> revisit this in a year it'll be there and it'll be super easy to set
> the value properly.

With above approch to set it in probe accordingly PSCI change won't be required.

it will be simple, cleaner and without any resistance from PSCI perspective.

>
>>         { }
>>  };
>>
>> diff --git a/drivers/soc/qcom/rpmh.c b/drivers/soc/qcom/rpmh.c
>> index 5bed8f4..9d40209 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;
>>  }
>> @@ -433,16 +430,63 @@ static int send_single(struct rpmh_ctrlr *ctrlr, enum rpmh_state state,
>>  }
>>
>>  /**
>> + * rpmh_start_transaction: Indicates start of rpmh transactions, this
>> + * must be ended by invoking rpmh_end_transaction().
>> + *
>> + * @dev: the device making the request
>> + */
>> +void rpmh_start_transaction(const struct device *dev)
>> +{
>> +       struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr(dev);
>> +       unsigned long flags;
>> +
>> +       if (!ctrlr->flush_dirty)
>> +               return;
>> +
>> +       spin_lock_irqsave(&ctrlr->cache_lock, flags);
>> +       ctrlr->active_clients++;
> Wouldn't hurt to have something like:
>
> /*
>  * Detect likely leak; we shouldn't have 1000
>  * people making in-flight changes at the same time.
>  */
> WARN_ON(ctrlr->active_clients > 1000)
Not necessary change.
>
>
>> +       spin_unlock_irqrestore(&ctrlr->cache_lock, flags);
>> +}
>> +EXPORT_SYMBOL(rpmh_start_transaction);
>> +
>> +/**
>> + * rpmh_end_transaction: Indicates end of rpmh transactions. All dirty data
>> + * in cache can be flushed immediately when ctrlr->flush_dirty is set
>> + *
>> + * @dev: the device making the request
>> + *
>> + * Return: 0 on success, error number otherwise.
>> + */
>> +int rpmh_end_transaction(const struct device *dev)
>> +{
>> +       struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr(dev);
>> +       unsigned long flags;
>> +       int ret = 0;
>> +
>> +       if (!ctrlr->flush_dirty)
>> +               return ret;
>> +
>> +       spin_lock_irqsave(&ctrlr->cache_lock, flags);
> WARN_ON(!active_clients);
Why? when active_clients become zero, we want to finally call rpmh_flush(), i don't see a reason to warn and then flush.

Or do you want to make a check if client really called rpmh_start_transaction() first before calling rpmh_end_transaction() then when we do
ctrlr->active_clients--;

it shouldn't go to negative value at the end. in that case let me know, i will make those changes.

>
>
>> +
>> +       ctrlr->active_clients--;
>> +       if (ctrlr->dirty && !ctrlr->active_clients)
>> +               ret = rpmh_flush(ctrlr);
> As mentioned previously [2], I don't think it's valid to call
> rpmh_flush() with interrupts disabled.  Specifically (as of your
> previous patch) rpmh_flush now loops if rpmh_rsc_invalidate() returns
> -EAGAIN.  I believe that the caller needs to enable interrupts for a
> little bit before trying again.  If the caller doesn't need to enable
> interrupts for a little bit before trying again then why was -EAGAIN
> even returned?  tcs_invalidate() could have just looped itself and all
> the code would be much simpler.
I will check and address this.
>
>
>> +
>> +       spin_unlock_irqrestore(&ctrlr->cache_lock, flags);
>> +
>> +       return ret;
>> +}
>> +EXPORT_SYMBOL(rpmh_end_transaction);
>> +
>> +/**
>>   * rpmh_flush: Flushes the buffered active and sleep sets to TCS
>>   *
>>   * @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)
>>  {
>> @@ -464,10 +508,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",
>> diff --git a/include/soc/qcom/rpmh.h b/include/soc/qcom/rpmh.h
>> index f9ec353..85e1ab2 100644
>> --- a/include/soc/qcom/rpmh.h
>> +++ b/include/soc/qcom/rpmh.h
>> @@ -22,6 +22,10 @@ int rpmh_write_batch(const struct device *dev, enum rpmh_state state,
>>
>>  int rpmh_invalidate(const struct device *dev);
>>
>> +void rpmh_start_transaction(const struct device *dev);
>> +
>> +int rpmh_end_transaction(const struct device *dev);
>> +
>>  #else
>>
>>  static inline int rpmh_write(const struct device *dev, enum rpmh_state state,
>> @@ -41,6 +45,12 @@ static inline int rpmh_write_batch(const struct device *dev,
>>  static inline int rpmh_invalidate(const struct device *dev)
>>  { return -ENODEV; }
>>
>> +void rpmh_start_transaction(const struct device *dev)
>> +{ return -ENODEV; }
> Unexpected return from void function.
>
Thanks, done.
>> +
>> +int rpmh_end_transaction(const struct device *dev)
>> +{ return -ENODEV; }
>> +
>>  #endif /* CONFIG_QCOM_RPMH */
>>
>>  #endif /* __SOC_QCOM_RPMH_H__ */
> [1] https://lore.kernel.org/r/CAD=FV=VzNnRdDN5uPYskJ6kQHq2bAi2ysEqt0=taagdd_qZb-g@mail.gmail.com
> [2] https://lore.kernel.org/r/CAD=FV=UYpO2rSOoF-OdZd3jKfSZGKnpQJPoiE5fzH+u1uafS6g@mail.gmail.com
> [3] https://lore.kernel.org/r/CAD=FV=VNaqwiti+UB8fLgjF5r2CD2xeF_p7qHS-_yXqf+ZDrBg@mail.gmail.com
>
>
>
> -Doug

Thanks,

Maulik
Doug Anderson March 10, 2020, 3:46 p.m. UTC | #3
Hi,

On Tue, Mar 10, 2020 at 4:19 AM Maulik Shah <mkshah@codeaurora.org> wrote:
>
>
> On 3/10/2020 5:13 AM, Doug Anderson wrote:
> > Hi,
> >
> > On Mon, Mar 9, 2020 at 2:31 AM Maulik Shah <mkshah@codeaurora.org> wrote:
> >> Add changes to invoke rpmh flush() from within cache_lock when the data in
> >> cache is dirty.
> >>
> >> Introduce two new APIs for this. Clients can use rpmh_start_transaction()
> >> before any rpmh transaction once done invoke rpmh_end_transaction() which
> >> internally invokes rpmh_flush() if the caches has become dirty.
> >>
> >> Add support to control this with flush_dirty flag.
> >>
> >> Signed-off-by: Maulik Shah <mkshah@codeaurora.org>
> >> Reviewed-by: Srinivas Rao L <lsrao@codeaurora.org>
> >> ---
> >>  drivers/soc/qcom/rpmh-internal.h |  4 +++
> >>  drivers/soc/qcom/rpmh-rsc.c      |  6 +++-
> >>  drivers/soc/qcom/rpmh.c          | 64 ++++++++++++++++++++++++++++++++--------
> >>  include/soc/qcom/rpmh.h          | 10 +++++++
> >>  4 files changed, 71 insertions(+), 13 deletions(-)
> > As mentioned previously but not addressed [3], I believe your series
> > breaks things if there are zero ACTIVE TCSs and you're using the
> > immediate-flush solution.  Specifically any attempt to set something's
> > "active" state will clobber the sleep/wake.  I believe this is hard to
> > fix, especially if you want rpmh_write_async() to work properly and
> > need to be robust to the last man going down while rpmh_write_async()
> > is running but hasn't finished.  My suggestion was to consider it to
> > be an error at probe time for now.
> >
> > Actually, though, I'd be super surprised if the "active == 0" case
> > works anyway.  Aside from subtle problems of not handling -EAGAIN (see
> > another previous message that you didn't respond to [2]), I think
> > you'll also get failures because you never enable interrupts in
> > RSC_DRV_IRQ_ENABLE for anything other than the ACTIVE_TCS.  Thus
> > you'll never get interrupts saying when your transactions on the
> > borrowed "wake" TCS finish.
>
> No, it shouldn’t effect even with "non-OSI-mode + 0 ACTIVE TCS"
>
> i just replied on v9, pasting same on v13 as well.
>
> After taking your suggestion to do rpmh start/end transaction() in v13, rpmh_end_transaction()
> invokes rpmh_flush() only for the last client and by this time expecting all of rpmh_write()
> and rpmh_write_batch() will be already “finished” as client first waits for them to finish
> and then only invokes end.
>
> So driver is good to handle rpmh_write() and rpmh_write_batch() calls.

Ah, right.  In the previous version of the patch it was a problem
because you flushed in cache_rpm_request() and then clobbered it right
away in __rpmh_write() when you did rpmh_rsc_send_data().  With v13
that is not the case anymore.  So this case should be OK.


> Regarding rpmh_write_async() call, which is a fire-n-forget request from SW and client driver
> may immediately invoke rpmh_end_transaction() after this.
>
> this case is also handled properly…
> Lets again take an example for understanding this..
>
> 1.    Client invokes rpmh_write_async() to send ACTIVE cmds for targets which has zero ACTIVE TCS
>
>     Rpmh driver Re-purposes one of SLEEP/WAKE TCS to use as ACTIVE, internally this also sets
>     drv->tcs_in_use to true for respective SLEEP/WAKE TCS.
>
> 2.    Client now without waiting for above to finish, goes ahead and invokes rpmh_end_transaction()
>     which calls rpmh_flush() (in case cache become dirty)

I guess we'd have to confirm that there's no way for the cache to
_not_ become dirty but you do an "active" transaction.  Let's imagine
this case:

start transaction
rpmh_write(RPMH_ACTIVE_ONLY_STATE, addr=0x10, data=0x99);
rpmh_write(RPMH_SLEEP_STATE, addr=0x10, data=0x11);
rpmh_write(RPMH_WAKE_ONLY_STATE, addr=0x10, data=0x99);
end transaction

start transaction
rpmh_write(RPMH_ACTIVE_ONLY_STATE, addr=0x10, data=0x99);
rpmh_write(RPMH_SLEEP_STATE, addr=0x10, data=0x11);
rpmh_write(RPMH_WAKE_ONLY_STATE, addr=0x10, data=0x99);
end transaction

In other words the client does the same sequence twice in a row with
no change in data.  After the first set I think you'd be fine.  ...but
after the second set you'll be in trouble.  That's because none of the
calls in the second set would cause the "dirty" to be set.  ...but for
"active only" calls we don't have any sort of cache--we just fire it
off.  When we fire off the active only we'll clobber the sleep/wake
TCS.  ...and then nothing will write them again because the cache
isn't dirty.


>     Now if re-purposed TCS is still in use in HW (transaction in progress), we still have
>     drv->tcs_in_use set. So the rpmh_rsc_invalidate() (invoked from rpmh_flush()) will keep on
>     returning -EAGAIN until that TCS becomes free to use and then goes ahead to finish its job.

Ah, interesting.  I still think you have problems I pointed out in
another response because you never enable interrupts for the "WAKE
TCS", but I can see how this could be made to work.  In this case
"async" becomes a little silly here because the flush will essentially
be forced to wait until the transaction is totally done (so the TCS is
free again), but it should be able to work.  This might actually point
out something that needs to be changed in my "clean up" series since I
guess "tcs_in_use" could sometimes be present in a wake TCS now.


> >> diff --git a/drivers/soc/qcom/rpmh-rsc.c b/drivers/soc/qcom/rpmh-rsc.c
> >> index e278fc1..b6391e1 100644
> >> --- a/drivers/soc/qcom/rpmh-rsc.c
> >> +++ b/drivers/soc/qcom/rpmh-rsc.c
> >> @@ -61,6 +61,8 @@
> >>  #define CMD_STATUS_ISSUED              BIT(8)
> >>  #define CMD_STATUS_COMPL               BIT(16)
> >>
> >> +#define FLUSH_DIRTY                    1
> >> +
> >>  static u32 read_tcs_reg(struct rsc_drv *drv, int reg, int tcs_id, int cmd_id)
> >>  {
> >>         return readl_relaxed(drv->tcs_base + reg + RSC_DRV_TCS_OFFSET * tcs_id +
> >> @@ -670,13 +672,15 @@ static int rpmh_rsc_probe(struct platform_device *pdev)
> >>         INIT_LIST_HEAD(&drv->client.cache);
> >>         INIT_LIST_HEAD(&drv->client.batch_cache);
> >>
> >> +       drv->client.flush_dirty = device_get_match_data(&pdev->dev);
> >> +
> >>         dev_set_drvdata(&pdev->dev, drv);
> >>
> >>         return devm_of_platform_populate(&pdev->dev);
> >>  }
> >>
> >>  static const struct of_device_id rpmh_drv_match[] = {
> >> -       { .compatible = "qcom,rpmh-rsc", },
> >> +       { .compatible = "qcom,rpmh-rsc", .data = (void *)FLUSH_DIRTY },
> > Ick.  This is just confusing.  IMO better to set
> > 'drv->client.flush_dirty = true' directly in probe with a comment
> > saying that it could be removed if we had OSI.
> Done.
> i will keep this bit earlier in probe with commet, so later if we detect rsc to be in hierarchy
> from [1], we can override this to be 0 within rpmh_probe_power_domain().
>
> [1] https://patchwork.kernel.org/patch/11391229/

I don't really understand, but maybe it'll be obvious when I see the code.



> > ...and while you're at it, why not fire off a separate patch (not in
> > your series) adding the stub to 'include/linux/psci.h'.  Then when we
> > revisit this in a year it'll be there and it'll be super easy to set
> > the value properly.
>
> With above approch to set it in probe accordingly PSCI change won't be required.
>
> it will be simple, cleaner and without any resistance from PSCI perspective.
>
> >
> >>         { }
> >>  };
> >>
> >> diff --git a/drivers/soc/qcom/rpmh.c b/drivers/soc/qcom/rpmh.c
> >> index 5bed8f4..9d40209 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;
> >>  }
> >> @@ -433,16 +430,63 @@ static int send_single(struct rpmh_ctrlr *ctrlr, enum rpmh_state state,
> >>  }
> >>
> >>  /**
> >> + * rpmh_start_transaction: Indicates start of rpmh transactions, this
> >> + * must be ended by invoking rpmh_end_transaction().
> >> + *
> >> + * @dev: the device making the request
> >> + */
> >> +void rpmh_start_transaction(const struct device *dev)
> >> +{
> >> +       struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr(dev);
> >> +       unsigned long flags;
> >> +
> >> +       if (!ctrlr->flush_dirty)
> >> +               return;
> >> +
> >> +       spin_lock_irqsave(&ctrlr->cache_lock, flags);
> >> +       ctrlr->active_clients++;
> > Wouldn't hurt to have something like:
> >
> > /*
> >  * Detect likely leak; we shouldn't have 1000
> >  * people making in-flight changes at the same time.
> >  */
> > WARN_ON(ctrlr->active_clients > 1000)
> Not necessary change.

Yes, but it will catch buggy clients much more quickly.  What are the downsides?


> >> +       spin_unlock_irqrestore(&ctrlr->cache_lock, flags);
> >> +}
> >> +EXPORT_SYMBOL(rpmh_start_transaction);
> >> +
> >> +/**
> >> + * rpmh_end_transaction: Indicates end of rpmh transactions. All dirty data
> >> + * in cache can be flushed immediately when ctrlr->flush_dirty is set
> >> + *
> >> + * @dev: the device making the request
> >> + *
> >> + * Return: 0 on success, error number otherwise.
> >> + */
> >> +int rpmh_end_transaction(const struct device *dev)
> >> +{
> >> +       struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr(dev);
> >> +       unsigned long flags;
> >> +       int ret = 0;
> >> +
> >> +       if (!ctrlr->flush_dirty)
> >> +               return ret;
> >> +
> >> +       spin_lock_irqsave(&ctrlr->cache_lock, flags);
> > WARN_ON(!active_clients);
> Why? when active_clients become zero, we want to finally call rpmh_flush(), i don't see a reason to warn and then flush.
>
> Or do you want to make a check if client really called rpmh_start_transaction() first before calling rpmh_end_transaction() then when we do
> ctrlr->active_clients--;
>
> it shouldn't go to negative value at the end. in that case let me know, i will make those changes.

Right, I want to warn on underflow.  AKA:

WARN_ON(!ctrlr->active_clients);
ctrlr->active_clients--;

Generally it's handy to detect mismatches of start/end.  It could be
that someone accidentally starts twice and ends once.  Starts zero
times and ends once.  Starts once and ends twice.  All of these are
interesting cases to warn about.



-Doug
Maulik Shah March 11, 2020, 6:36 a.m. UTC | #4
Hi,

On 3/10/2020 9:16 PM, Doug Anderson wrote:
> Hi,
>
> On Tue, Mar 10, 2020 at 4:19 AM Maulik Shah <mkshah@codeaurora.org> wrote:
>>
>> On 3/10/2020 5:13 AM, Doug Anderson wrote:
>>> Hi,
>>>
>>> On Mon, Mar 9, 2020 at 2:31 AM Maulik Shah <mkshah@codeaurora.org> wrote:
>>>> Add changes to invoke rpmh flush() from within cache_lock when the data in
>>>> cache is dirty.
>>>>
>>>> Introduce two new APIs for this. Clients can use rpmh_start_transaction()
>>>> before any rpmh transaction once done invoke rpmh_end_transaction() which
>>>> internally invokes rpmh_flush() if the caches has become dirty.
>>>>
>>>> Add support to control this with flush_dirty flag.
>>>>
>>>> Signed-off-by: Maulik Shah <mkshah@codeaurora.org>
>>>> Reviewed-by: Srinivas Rao L <lsrao@codeaurora.org>
>>>> ---
>>>>  drivers/soc/qcom/rpmh-internal.h |  4 +++
>>>>  drivers/soc/qcom/rpmh-rsc.c      |  6 +++-
>>>>  drivers/soc/qcom/rpmh.c          | 64 ++++++++++++++++++++++++++++++++--------
>>>>  include/soc/qcom/rpmh.h          | 10 +++++++
>>>>  4 files changed, 71 insertions(+), 13 deletions(-)
>>> As mentioned previously but not addressed [3], I believe your series
>>> breaks things if there are zero ACTIVE TCSs and you're using the
>>> immediate-flush solution.  Specifically any attempt to set something's
>>> "active" state will clobber the sleep/wake.  I believe this is hard to
>>> fix, especially if you want rpmh_write_async() to work properly and
>>> need to be robust to the last man going down while rpmh_write_async()
>>> is running but hasn't finished.  My suggestion was to consider it to
>>> be an error at probe time for now.
>>>
>>> Actually, though, I'd be super surprised if the "active == 0" case
>>> works anyway.  Aside from subtle problems of not handling -EAGAIN (see
>>> another previous message that you didn't respond to [2]), I think
>>> you'll also get failures because you never enable interrupts in
>>> RSC_DRV_IRQ_ENABLE for anything other than the ACTIVE_TCS.  Thus
>>> you'll never get interrupts saying when your transactions on the
>>> borrowed "wake" TCS finish.
>> No, it shouldn’t effect even with "non-OSI-mode + 0 ACTIVE TCS"
>>
>> i just replied on v9, pasting same on v13 as well.
>>
>> After taking your suggestion to do rpmh start/end transaction() in v13, rpmh_end_transaction()
>> invokes rpmh_flush() only for the last client and by this time expecting all of rpmh_write()
>> and rpmh_write_batch() will be already “finished” as client first waits for them to finish
>> and then only invokes end.
>>
>> So driver is good to handle rpmh_write() and rpmh_write_batch() calls.
> Ah, right.  In the previous version of the patch it was a problem
> because you flushed in cache_rpm_request() and then clobbered it right
> away in __rpmh_write() when you did rpmh_rsc_send_data().  With v13
> that is not the case anymore.  So this case should be OK.
>
>
>> Regarding rpmh_write_async() call, which is a fire-n-forget request from SW and client driver
>> may immediately invoke rpmh_end_transaction() after this.
>>
>> this case is also handled properly…
>> Lets again take an example for understanding this..
>>
>> 1.    Client invokes rpmh_write_async() to send ACTIVE cmds for targets which has zero ACTIVE TCS
>>
>>     Rpmh driver Re-purposes one of SLEEP/WAKE TCS to use as ACTIVE, internally this also sets
>>     drv->tcs_in_use to true for respective SLEEP/WAKE TCS.
>>
>> 2.    Client now without waiting for above to finish, goes ahead and invokes rpmh_end_transaction()
>>     which calls rpmh_flush() (in case cache become dirty)
> I guess we'd have to confirm that there's no way for the cache to
> _not_ become dirty but you do an "active" transaction.  Let's imagine
> this case:
>
> start transaction
> rpmh_write(RPMH_ACTIVE_ONLY_STATE, addr=0x10, data=0x99);
> rpmh_write(RPMH_SLEEP_STATE, addr=0x10, data=0x11);
> rpmh_write(RPMH_WAKE_ONLY_STATE, addr=0x10, data=0x99);
> end transaction
>
> start transaction
> rpmh_write(RPMH_ACTIVE_ONLY_STATE, addr=0x10, data=0x99);
> rpmh_write(RPMH_SLEEP_STATE, addr=0x10, data=0x11);
> rpmh_write(RPMH_WAKE_ONLY_STATE, addr=0x10, data=0x99);
> end transaction
>
> In other words the client does the same sequence twice in a row with
> no change in data.  After the first set I think you'd be fine.  ...but
> after the second set you'll be in trouble.  That's because none of the
> calls in the second set would cause the "dirty" to be set.  ...but for
> "active only" calls we don't have any sort of cache--we just fire it
> off.  When we fire off the active only we'll clobber the sleep/wake
> TCS.  ...and then nothing will write them again because the cache
> isn't dirty.
Agree with above scenario, but i don't see a reason why would a rpmh client send the same request twice.

Let me explain...

while first request is a proper one (already handled in rpmh driver), second is again duplicate
of first without any change.

when this duplicate request is triggered, resource may be in its own low power mode, when this
extra/duplicate command is sent, it needs to come out of low power mode and apply the newly requested
level but it is already at that level, so it will immediatly ack back without doing any real level
transition, and it will again go back to sleep. so there can be a small power hit.

and also for "ACTIVE" we need to handle this unnecessary ack interrupt at CPU, so CPU
(if it is in some low power mode where this interrupt is affined to) need to wake up and
handle this interrupt, again taking possible power hit from CPU point.

whats more?

while this whole unnecessary ping-pong happens in HW and SW, client may be waiting if its a blocking call.

rpmh client is expected to "aggregate" and finally do rpmh transaction "only if"
aggregated final level differs from what resource is already having.

if they are not doing this, then IMO, this is something to be addressed from client side.

>
>>     Now if re-purposed TCS is still in use in HW (transaction in progress), we still have
>>     drv->tcs_in_use set. So the rpmh_rsc_invalidate() (invoked from rpmh_flush()) will keep on
>>     returning -EAGAIN until that TCS becomes free to use and then goes ahead to finish its job.
> Ah, interesting.  I still think you have problems I pointed out in
> another response because you never enable interrupts for the "WAKE
> TCS", but I can see how this could be made to work.  In this case
> "async" becomes a little silly here because the flush will essentially
> be forced to wait until the transaction is totally done (so the TCS is
> free again), but it should be able to work.  
Agree, but i guess, this is a hit expected with non-OSI to do rpm_flush() immediately.
> This might actually point
> out something that needs to be changed in my "clean up" series since I
> guess "tcs_in_use" could sometimes be present in a wake TCS now.
yes this still need to keep.
>
>
>>>> diff --git a/drivers/soc/qcom/rpmh-rsc.c b/drivers/soc/qcom/rpmh-rsc.c
>>>> index e278fc1..b6391e1 100644
>>>> --- a/drivers/soc/qcom/rpmh-rsc.c
>>>> +++ b/drivers/soc/qcom/rpmh-rsc.c
>>>> @@ -61,6 +61,8 @@
>>>>  #define CMD_STATUS_ISSUED              BIT(8)
>>>>  #define CMD_STATUS_COMPL               BIT(16)
>>>>
>>>> +#define FLUSH_DIRTY                    1
>>>> +
>>>>  static u32 read_tcs_reg(struct rsc_drv *drv, int reg, int tcs_id, int cmd_id)
>>>>  {
>>>>         return readl_relaxed(drv->tcs_base + reg + RSC_DRV_TCS_OFFSET * tcs_id +
>>>> @@ -670,13 +672,15 @@ static int rpmh_rsc_probe(struct platform_device *pdev)
>>>>         INIT_LIST_HEAD(&drv->client.cache);
>>>>         INIT_LIST_HEAD(&drv->client.batch_cache);
>>>>
>>>> +       drv->client.flush_dirty = device_get_match_data(&pdev->dev);
>>>> +
>>>>         dev_set_drvdata(&pdev->dev, drv);
>>>>
>>>>         return devm_of_platform_populate(&pdev->dev);
>>>>  }
>>>>
>>>>  static const struct of_device_id rpmh_drv_match[] = {
>>>> -       { .compatible = "qcom,rpmh-rsc", },
>>>> +       { .compatible = "qcom,rpmh-rsc", .data = (void *)FLUSH_DIRTY },
>>> Ick.  This is just confusing.  IMO better to set
>>> 'drv->client.flush_dirty = true' directly in probe with a comment
>>> saying that it could be removed if we had OSI.
>> Done.
>> i will keep this bit earlier in probe with commet, so later if we detect rsc to be in hierarchy
>> from [1], we can override this to be 0 within rpmh_probe_power_domain().
>>
>> [1] https://patchwork.kernel.org/patch/11391229/
> I don't really understand, but maybe it'll be obvious when I see the code.
okay.
>
>
>
>>> ...and while you're at it, why not fire off a separate patch (not in
>>> your series) adding the stub to 'include/linux/psci.h'.  Then when we
>>> revisit this in a year it'll be there and it'll be super easy to set
>>> the value properly.
>> With above approch to set it in probe accordingly PSCI change won't be required.
>>
>> it will be simple, cleaner and without any resistance from PSCI perspective.
>>
>>>>         { }
>>>>  };
>>>>
>>>> diff --git a/drivers/soc/qcom/rpmh.c b/drivers/soc/qcom/rpmh.c
>>>> index 5bed8f4..9d40209 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;
>>>>  }
>>>> @@ -433,16 +430,63 @@ static int send_single(struct rpmh_ctrlr *ctrlr, enum rpmh_state state,
>>>>  }
>>>>
>>>>  /**
>>>> + * rpmh_start_transaction: Indicates start of rpmh transactions, this
>>>> + * must be ended by invoking rpmh_end_transaction().
>>>> + *
>>>> + * @dev: the device making the request
>>>> + */
>>>> +void rpmh_start_transaction(const struct device *dev)
>>>> +{
>>>> +       struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr(dev);
>>>> +       unsigned long flags;
>>>> +
>>>> +       if (!ctrlr->flush_dirty)
>>>> +               return;
>>>> +
>>>> +       spin_lock_irqsave(&ctrlr->cache_lock, flags);
>>>> +       ctrlr->active_clients++;
>>> Wouldn't hurt to have something like:
>>>
>>> /*
>>>  * Detect likely leak; we shouldn't have 1000
>>>  * people making in-flight changes at the same time.
>>>  */
>>> WARN_ON(ctrlr->active_clients > 1000)
>> Not necessary change.
> Yes, but it will catch buggy clients much more quickly.  What are the downsides?
IMO, its uncessary warning that too with arbitrary number (1000).
rpmh clients are not expected to bombard it with thousands of requests, as i explained
above, they need to aggregate and make rpmh request only when real level transistion
needed.

and there is already a message (in rpmh_rsc_send_data()) to tell if all TCS are occupied
and rpmh will retry to send pending request. "TCS Busy, retrying RPMH message send"
>
>
>>>> +       spin_unlock_irqrestore(&ctrlr->cache_lock, flags);
>>>> +}
>>>> +EXPORT_SYMBOL(rpmh_start_transaction);
>>>> +
>>>> +/**
>>>> + * rpmh_end_transaction: Indicates end of rpmh transactions. All dirty data
>>>> + * in cache can be flushed immediately when ctrlr->flush_dirty is set
>>>> + *
>>>> + * @dev: the device making the request
>>>> + *
>>>> + * Return: 0 on success, error number otherwise.
>>>> + */
>>>> +int rpmh_end_transaction(const struct device *dev)
>>>> +{
>>>> +       struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr(dev);
>>>> +       unsigned long flags;
>>>> +       int ret = 0;
>>>> +
>>>> +       if (!ctrlr->flush_dirty)
>>>> +               return ret;
>>>> +
>>>> +       spin_lock_irqsave(&ctrlr->cache_lock, flags);
>>> WARN_ON(!active_clients);
>> Why? when active_clients become zero, we want to finally call rpmh_flush(), i don't see a reason to warn and then flush.
>>
>> Or do you want to make a check if client really called rpmh_start_transaction() first before calling rpmh_end_transaction() then when we do
>> ctrlr->active_clients--;
>>
>> it shouldn't go to negative value at the end. in that case let me know, i will make those changes.
> Right, I want to warn on underflow.  AKA:
>
> WARN_ON(!ctrlr->active_clients);
> ctrlr->active_clients--;
>
> Generally it's handy to detect mismatches of start/end.  It could be
> that someone accidentally starts twice and ends once.  Starts zero
> times and ends once.  Starts once and ends twice.  All of these are
> interesting cases to warn about.
>
>
>
> -Doug

Agree, i will address this.

Thanks,
Maulik
Doug Anderson March 11, 2020, 11:06 p.m. UTC | #5
Hi,

On Tue, Mar 10, 2020 at 11:36 PM Maulik Shah <mkshah@codeaurora.org> wrote:
>
> Hi,
>
> On 3/10/2020 9:16 PM, Doug Anderson wrote:
> > Hi,
> >
> > On Tue, Mar 10, 2020 at 4:19 AM Maulik Shah <mkshah@codeaurora.org> wrote:
> >>
> >> On 3/10/2020 5:13 AM, Doug Anderson wrote:
> >>> Hi,
> >>>
> >>> On Mon, Mar 9, 2020 at 2:31 AM Maulik Shah <mkshah@codeaurora.org> wrote:
> >>>> Add changes to invoke rpmh flush() from within cache_lock when the data in
> >>>> cache is dirty.
> >>>>
> >>>> Introduce two new APIs for this. Clients can use rpmh_start_transaction()
> >>>> before any rpmh transaction once done invoke rpmh_end_transaction() which
> >>>> internally invokes rpmh_flush() if the caches has become dirty.
> >>>>
> >>>> Add support to control this with flush_dirty flag.
> >>>>
> >>>> Signed-off-by: Maulik Shah <mkshah@codeaurora.org>
> >>>> Reviewed-by: Srinivas Rao L <lsrao@codeaurora.org>
> >>>> ---
> >>>>  drivers/soc/qcom/rpmh-internal.h |  4 +++
> >>>>  drivers/soc/qcom/rpmh-rsc.c      |  6 +++-
> >>>>  drivers/soc/qcom/rpmh.c          | 64 ++++++++++++++++++++++++++++++++--------
> >>>>  include/soc/qcom/rpmh.h          | 10 +++++++
> >>>>  4 files changed, 71 insertions(+), 13 deletions(-)
> >>> As mentioned previously but not addressed [3], I believe your series
> >>> breaks things if there are zero ACTIVE TCSs and you're using the
> >>> immediate-flush solution.  Specifically any attempt to set something's
> >>> "active" state will clobber the sleep/wake.  I believe this is hard to
> >>> fix, especially if you want rpmh_write_async() to work properly and
> >>> need to be robust to the last man going down while rpmh_write_async()
> >>> is running but hasn't finished.  My suggestion was to consider it to
> >>> be an error at probe time for now.
> >>>
> >>> Actually, though, I'd be super surprised if the "active == 0" case
> >>> works anyway.  Aside from subtle problems of not handling -EAGAIN (see
> >>> another previous message that you didn't respond to [2]), I think
> >>> you'll also get failures because you never enable interrupts in
> >>> RSC_DRV_IRQ_ENABLE for anything other than the ACTIVE_TCS.  Thus
> >>> you'll never get interrupts saying when your transactions on the
> >>> borrowed "wake" TCS finish.
> >> No, it shouldn’t effect even with "non-OSI-mode + 0 ACTIVE TCS"
> >>
> >> i just replied on v9, pasting same on v13 as well.
> >>
> >> After taking your suggestion to do rpmh start/end transaction() in v13, rpmh_end_transaction()
> >> invokes rpmh_flush() only for the last client and by this time expecting all of rpmh_write()
> >> and rpmh_write_batch() will be already “finished” as client first waits for them to finish
> >> and then only invokes end.
> >>
> >> So driver is good to handle rpmh_write() and rpmh_write_batch() calls.
> > Ah, right.  In the previous version of the patch it was a problem
> > because you flushed in cache_rpm_request() and then clobbered it right
> > away in __rpmh_write() when you did rpmh_rsc_send_data().  With v13
> > that is not the case anymore.  So this case should be OK.
> >
> >
> >> Regarding rpmh_write_async() call, which is a fire-n-forget request from SW and client driver
> >> may immediately invoke rpmh_end_transaction() after this.
> >>
> >> this case is also handled properly…
> >> Lets again take an example for understanding this..
> >>
> >> 1.    Client invokes rpmh_write_async() to send ACTIVE cmds for targets which has zero ACTIVE TCS
> >>
> >>     Rpmh driver Re-purposes one of SLEEP/WAKE TCS to use as ACTIVE, internally this also sets
> >>     drv->tcs_in_use to true for respective SLEEP/WAKE TCS.
> >>
> >> 2.    Client now without waiting for above to finish, goes ahead and invokes rpmh_end_transaction()
> >>     which calls rpmh_flush() (in case cache become dirty)
> > I guess we'd have to confirm that there's no way for the cache to
> > _not_ become dirty but you do an "active" transaction.  Let's imagine
> > this case:
> >
> > start transaction
> > rpmh_write(RPMH_ACTIVE_ONLY_STATE, addr=0x10, data=0x99);
> > rpmh_write(RPMH_SLEEP_STATE, addr=0x10, data=0x11);
> > rpmh_write(RPMH_WAKE_ONLY_STATE, addr=0x10, data=0x99);
> > end transaction
> >
> > start transaction
> > rpmh_write(RPMH_ACTIVE_ONLY_STATE, addr=0x10, data=0x99);
> > rpmh_write(RPMH_SLEEP_STATE, addr=0x10, data=0x11);
> > rpmh_write(RPMH_WAKE_ONLY_STATE, addr=0x10, data=0x99);
> > end transaction
> >
> > In other words the client does the same sequence twice in a row with
> > no change in data.  After the first set I think you'd be fine.  ...but
> > after the second set you'll be in trouble.  That's because none of the
> > calls in the second set would cause the "dirty" to be set.  ...but for
> > "active only" calls we don't have any sort of cache--we just fire it
> > off.  When we fire off the active only we'll clobber the sleep/wake
> > TCS.  ...and then nothing will write them again because the cache
> > isn't dirty.
> Agree with above scenario, but i don't see a reason why would a rpmh client send the same request twice.
>
> Let me explain...
>
> while first request is a proper one (already handled in rpmh driver), second is again duplicate
> of first without any change.
>
> when this duplicate request is triggered, resource may be in its own low power mode, when this
> extra/duplicate command is sent, it needs to come out of low power mode and apply the newly requested
> level but it is already at that level, so it will immediatly ack back without doing any real level
> transition, and it will again go back to sleep. so there can be a small power hit.
>
> and also for "ACTIVE" we need to handle this unnecessary ack interrupt at CPU, so CPU
> (if it is in some low power mode where this interrupt is affined to) need to wake up and
> handle this interrupt, again taking possible power hit from CPU point.
>
> whats more?
>
> while this whole unnecessary ping-pong happens in HW and SW, client may be waiting if its a blocking call.
>
> rpmh client is expected to "aggregate" and finally do rpmh transaction "only if"
> aggregated final level differs from what resource is already having.
>
> if they are not doing this, then IMO, this is something to be addressed from client side.

It feels like "rpmh.c" needs to add "active_val" to its 'struct
cache_req' or truly enforce that "active_val == wake_val" all the time
(in other words if someone sets "wake_val" in rpmh_write() /
rpmh_write_async() you should also set "active_val").  Now you can
skip the call to rpmh-rsc if the active doesn't change and the problem
is solved.

Specifically I wouldn't trust all callers of rpmh_write() /
rpmh_write_async() to never send the same data.  If it was just an
speed/power optimization then sure you could trust them, but this is
for correctness.


> >>     Now if re-purposed TCS is still in use in HW (transaction in progress), we still have
> >>     drv->tcs_in_use set. So the rpmh_rsc_invalidate() (invoked from rpmh_flush()) will keep on
> >>     returning -EAGAIN until that TCS becomes free to use and then goes ahead to finish its job.
> > Ah, interesting.  I still think you have problems I pointed out in
> > another response because you never enable interrupts for the "WAKE
> > TCS", but I can see how this could be made to work.  In this case
> > "async" becomes a little silly here because the flush will essentially
> > be forced to wait until the transaction is totally done (so the TCS is
> > free again), but it should be able to work.
> Agree, but i guess, this is a hit expected with non-OSI to do rpm_flush() immediately.

Right, though the hit is much much more if there is no active TCS.
Said another way: if there is an active TCS than non-OSI mode just
causes a bunch of extra register writes.  ...but if there is no active
TCS then non-OSI mode essentially makes rpmh_write_async() useless.

Hrmm, thinking about this again, though, I'm still not convinced I
understand what prevents the firmware from triggering "sleep mode"
while the sleep/wake TCS is being borrowed for an active-only
transaction.  If we're sitting in rpmh_write() and sleeping in
wait_for_completion_timeout() couldn't the system go idle and trigger
sleep mode?  In OSI-mode (with last man down) you'll always have a
rpmh_flush() called by the last man down so you know you can make sure
you're in a consistent state (one final flush and no more active-only
transactions will happen).  Without last man down you have to assume
it can happen at any time don't you?

...so I guess I'll go back to asserting that zero-active-TCS is
incompatible with non-OSI unless you have some way to prevent the
sleep mode from being triggered while you've borrowed the wake TCS.


> > This might actually point
> > out something that needs to be changed in my "clean up" series since I
> > guess "tcs_in_use" could sometimes be present in a wake TCS now.
> yes this still need to keep.

As I've looked at this more, I now believe that "tcs_in_use" is not
sufficient for that case.  Specifically nothing prevents another
thread writing the sleep/wake TCS right after it was invalidated but
before the active-only command is programmed into it.  Specifically:

- write sleep/wake
- write active only
-> see zero-active only and invalidate sleep/wake
-> another thread comes in and write sleep/wake
   ...NOTE: "tcs_in_use" isn't updated for sleep/wake
-> thread writing active_only will then program active_only
   atop the sleep/wake requests.

Maybe it's not a huge deal in the "OSI" case because you only ever
write sleep/wake in the last man down and there can be no new active
transactions when you're doing this.  ...but it seems like it'd be a
problem for non-OSI.

This whole "no active TCS" is really quite a mess.  Given how broken
it seems to me it almost feels like we should remove "no active TCS"
from the driver for now and then re-add it in a later patch when we
can really validate everything.  I tried addressing this in my
rpmh-rsc cleanup series and every time I thought I had a good solution
I could find another way to break it.

Do you actually have the "no active TCS" case working on the current
code, or does it only work on some downstream variant of the driver?


> >>>> diff --git a/drivers/soc/qcom/rpmh-rsc.c b/drivers/soc/qcom/rpmh-rsc.c
> >>>> index e278fc1..b6391e1 100644
> >>>> --- a/drivers/soc/qcom/rpmh-rsc.c
> >>>> +++ b/drivers/soc/qcom/rpmh-rsc.c
> >>>> @@ -61,6 +61,8 @@
> >>>>  #define CMD_STATUS_ISSUED              BIT(8)
> >>>>  #define CMD_STATUS_COMPL               BIT(16)
> >>>>
> >>>> +#define FLUSH_DIRTY                    1
> >>>> +
> >>>>  static u32 read_tcs_reg(struct rsc_drv *drv, int reg, int tcs_id, int cmd_id)
> >>>>  {
> >>>>         return readl_relaxed(drv->tcs_base + reg + RSC_DRV_TCS_OFFSET * tcs_id +
> >>>> @@ -670,13 +672,15 @@ static int rpmh_rsc_probe(struct platform_device *pdev)
> >>>>         INIT_LIST_HEAD(&drv->client.cache);
> >>>>         INIT_LIST_HEAD(&drv->client.batch_cache);
> >>>>
> >>>> +       drv->client.flush_dirty = device_get_match_data(&pdev->dev);
> >>>> +
> >>>>         dev_set_drvdata(&pdev->dev, drv);
> >>>>
> >>>>         return devm_of_platform_populate(&pdev->dev);
> >>>>  }
> >>>>
> >>>>  static const struct of_device_id rpmh_drv_match[] = {
> >>>> -       { .compatible = "qcom,rpmh-rsc", },
> >>>> +       { .compatible = "qcom,rpmh-rsc", .data = (void *)FLUSH_DIRTY },
> >>> Ick.  This is just confusing.  IMO better to set
> >>> 'drv->client.flush_dirty = true' directly in probe with a comment
> >>> saying that it could be removed if we had OSI.
> >> Done.
> >> i will keep this bit earlier in probe with commet, so later if we detect rsc to be in hierarchy
> >> from [1], we can override this to be 0 within rpmh_probe_power_domain().
> >>
> >> [1] https://patchwork.kernel.org/patch/11391229/
> > I don't really understand, but maybe it'll be obvious when I see the code.
> okay.
> >
> >
> >
> >>> ...and while you're at it, why not fire off a separate patch (not in
> >>> your series) adding the stub to 'include/linux/psci.h'.  Then when we
> >>> revisit this in a year it'll be there and it'll be super easy to set
> >>> the value properly.
> >> With above approch to set it in probe accordingly PSCI change won't be required.
> >>
> >> it will be simple, cleaner and without any resistance from PSCI perspective.
> >>
> >>>>         { }
> >>>>  };
> >>>>
> >>>> diff --git a/drivers/soc/qcom/rpmh.c b/drivers/soc/qcom/rpmh.c
> >>>> index 5bed8f4..9d40209 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;
> >>>>  }
> >>>> @@ -433,16 +430,63 @@ static int send_single(struct rpmh_ctrlr *ctrlr, enum rpmh_state state,
> >>>>  }
> >>>>
> >>>>  /**
> >>>> + * rpmh_start_transaction: Indicates start of rpmh transactions, this
> >>>> + * must be ended by invoking rpmh_end_transaction().
> >>>> + *
> >>>> + * @dev: the device making the request
> >>>> + */
> >>>> +void rpmh_start_transaction(const struct device *dev)
> >>>> +{
> >>>> +       struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr(dev);
> >>>> +       unsigned long flags;
> >>>> +
> >>>> +       if (!ctrlr->flush_dirty)
> >>>> +               return;
> >>>> +
> >>>> +       spin_lock_irqsave(&ctrlr->cache_lock, flags);
> >>>> +       ctrlr->active_clients++;
> >>> Wouldn't hurt to have something like:
> >>>
> >>> /*
> >>>  * Detect likely leak; we shouldn't have 1000
> >>>  * people making in-flight changes at the same time.
> >>>  */
> >>> WARN_ON(ctrlr->active_clients > 1000)
> >> Not necessary change.
> > Yes, but it will catch buggy clients much more quickly.  What are the downsides?
> IMO, its uncessary warning that too with arbitrary number (1000).
> rpmh clients are not expected to bombard it with thousands of requests, as i explained
> above, they need to aggregate and make rpmh request only when real level transistion
> needed.

I'm not saying we should limit the total number of requests to 1000.
I'm saying that if there are 1000 active clients then that's a
problem.  Right now there are something like 4 clients.  It doesn't
matter how fast those clients are sending, active_clients will only be
at most 4 and that would only be if they were all running their code
at the exact same time.

I want to be able to quickly detect this type of bug:

start_transaction()
ret = rpmh_write()
if (ret)
  return ret;
ret = rpmh_write()
end_transaction()
return ret;

...in other words: someone has a code path where start_transaction()
is called but never end_transaction().  I'm proposing that if we ever
see the ridiculous value of 1000 active clients the only way it could
happen is if one of the clients started more times than they ended.


I guess maybe by the time there were 1000 it would be too late,
though, because we'd have skipped A LOT of flushes by then?  Maybe
instead we should add something where if RPMH is "ditry" for more than
a certain amount of time we put a warning?


-Doug
Doug Anderson March 12, 2020, 3:11 p.m. UTC | #6
Hi,

Quoting below may look funny since you replied with HTML mail and all
old quoting was lost.  :(  I tried my best.

On Thu, Mar 12, 2020 at 3:32 AM Maulik Shah <mkshah@codeaurora.org> wrote:
>
> > Specifically I wouldn't trust all callers of rpmh_write() /
> > rpmh_write_async() to never send the same data.  If it was just an
> > speed/power optimization then sure you could trust them, but this is
> > for correctness.
>
> yes we should trust callers not to send duplicate data.

I guess we'll see how this works out.  Since this only affects the
"zero active-only" case and I'm pretty sure that case has more
important issues, I'm OK w/ ignoring for now.


> > Hrmm, thinking about this again, though, I'm still not convinced I
> > understand what prevents the firmware from triggering "sleep mode"
> > while the sleep/wake TCS is being borrowed for an active-only
> > transaction.  If we're sitting in rpmh_write() and sleeping in
> > wait_for_completion_timeout() couldn't the system go idle and trigger
> > sleep mode?  In OSI-mode (with last man down) you'll always have a
> > rpmh_flush() called by the last man down so you know you can make sure
> > you're in a consistent state (one final flush and no more active-only
> transactions will happen).  Without last man down you have to assume
> > it can happen at any time don't you?
>>
> > ...so I guess I'll go back to asserting that zero-active-TCS is
> > incompatible with non-OSI unless you have some way to prevent the
> > sleep mode from being triggered while you've borrowed the wake TCS.
>
> i had change for this in v4 to handle same.
>
> Link: https://patchwork.kernel.org/patch/11366205/
>
> + /*
> + * RPMh domain can not be powered off when there is pending ACK for
> + * ACTIVE_TCS request. Exit when controller is busy.
> + */
>
> before calling rpmh_flush() we check ctrlr is idle (ensuring
>
> tcs_is_free() check passes)  this will ensure that
>
> previous active transaction is completed before going ahead.
>
> i will add this check in v14.
>
> since this curretntly check for ACTIVE TCS only, i will update it to check the repurposed "WAKE TCS" is also free.

The difficulty isn't in adding a simple check, it's in adding a check
that is race free and handles locking properly.  Specifically looking
at your the v4 you pointed to, I see things like this:

  if (!rpmh_rsc_ctrlr_is_idle(drv))
    return -EBUSY;
  return rpmh_flush(&drv->client);

The rpmh_rsc_ctrlr_is_idle() grabs a spin lock implying that there
could be other people acting on RPMh at the same time (otherwise, why
do you even need locks?).  ...but when it returns the lock is
released.  Once the lock is dropped then other clients are free to
start using RPMH because nothing prevents them.  ...then you go ahead
and start flushing.

Said another way: due to the way you've structured locking in that
patch rpmh_rsc_ctrlr_is_idle() is inherently dangerous because it
returns an instantaneous state that may well have changed between the
spin_unlock_irqrestore() at the end of the function and when the
function returns.

You could, of course, fix this by requiring that the caller hold the
'drv->lock' for both the calls to rpmh_rsc_ctrlr_is_idle() and
rpmh_flush() (ignoring the fact the drv->lock is nominally part of
rpmh-rsc.c and not rpmh.c).  Now it would be safe from the problem I
described.  ...but now you get into a new problem.  If you ever hold
two locks you always need to make sure you grab them in the same order
any time you grab them both.  ...but tcs_write() we first grab a
tcs_lock and _then_ drv->lock.  That means the "fix" of holding
drv->lock for both the calls to rpmh_rsc_ctrlr_is_idle() and
rpmh_flush() won't work because rpmh_flush() will need to grab a
tcs_lock.  Possibly we could make this work by eliminating the "tcs
lock" and just having the one big "drv->lock" protect everything (or
introducing a new "super lock" making the other two meaningless).  It
would certainly be easier to reason about...

NOTE: the only way I'm able to reason about all the above things is
because I spent all the time to document what rpmh-rsc is doing and
what assumptions the various functions had [1].  It'd be great if that
could get a review.


> > This whole "no active TCS" is really quite a mess.  Given how broken
> > it seems to me it almost feels like we should remove "no active TCS"
> > from the driver for now and then re-add it in a later patch when we
> > can really validate everything.  I tried addressing this in my
> > rpmh-rsc cleanup series and every time I thought I had a good solution
> > I could find another way to break it.
> >
> > Do you actually have the "no active TCS" case working on the current
> > code, or does it only work on some downstream variant of the driver?
> >
> > It works fine on downstream variant. Some checks are still needed like above from v4
> >
> > since we do rpmh_flush() immediatly for dirty caches now.

OK.  So I take it you haven't tested the "zero active" case with the
upstream code?  In theory it should be easy to test, right?  Just hack
the driver to pretend there are zero active TCSs?

Which SoCs in specific need the zero active TCSs?  We are spending a
lot of time talking about this and reviewing the code with this in
mind.  It adds a lot of complexity to the driver.  If nothing under
active development needs it I think we should do ourselves a favor and
remove it for now, then add it back later.  Otherwise this whole
process is just going to take a lot more time.


> > I'm not saying we should limit the total number of requests to 1000.
> > I'm saying that if there are 1000 active clients then that's a
> > problem.  Right now there are something like 4 clients.  It doesn't
> > matter how fast those clients are sending, active_clients will only be
> > at most 4 and that would only be if they were all running their code
> > at the exact same time.
> >
> > I want to be able to quickly detect this type of bug:
> >
> > start_transaction()
> > ret = rpmh_write()
> > if (ret)
> >   return ret;
> > ret = rpmh_write()
> > end_transaction()
> > return ret;
> >
> > ...in other words: someone has a code path where start_transaction()
> > is called but never end_transaction().  I'm proposing that if we ever
> > see the ridiculous value of 1000 active clients the only way it could
> > happen is if one of the clients started more times than they ended.
> >
> >
> > I guess maybe by the time there were 1000 it would be too late,
> > though, because we'd have skipped A LOT of flushes by then?  Maybe
> > instead we should add something where if RPMH is "ditry" for more than
> > a certain amount of time we put a warning?
>
> IMO, we should not add any such warning with any number.
> There are only 4 clients and unlikely to have any new ones. those 4 we should be able to ensure
> that they invoke end_transaction(), if they have already done start_transaction().beside,
> Function description also says that "this must be ended by invoking rpmh_end_transaction()"
> i am ok to also add  saying that "rpmh do not check this, so its caller's responsibility to
> end it"

I don't agree but I won't argue further.  If you want to leave out the
WARN() then so be it.

-Doug

[1] https://lore.kernel.org/r/20200311161104.RFT.v2.5.I52653eb85d7dc8981ee0dafcd0b6cc0f273e9425@changeid
Maulik Shah March 25, 2020, 5:15 p.m. UTC | #7
Hi,

On 3/12/2020 8:41 PM, Doug Anderson wrote:
> Hi,
>
> Quoting below may look funny since you replied with HTML mail and all
> old quoting was lost.  :(  I tried my best.
>
> On Thu, Mar 12, 2020 at 3:32 AM Maulik Shah <mkshah@codeaurora.org> wrote:
>>> Specifically I wouldn't trust all callers of rpmh_write() /
>>> rpmh_write_async() to never send the same data.  If it was just an
>>> speed/power optimization then sure you could trust them, but this is
>>> for correctness.
>> yes we should trust callers not to send duplicate data.
> I guess we'll see how this works out.  Since this only affects the
> "zero active-only" case and I'm pretty sure that case has more
> important issues, I'm OK w/ ignoring for now.
>
>
>>> Hrmm, thinking about this again, though, I'm still not convinced I
>>> understand what prevents the firmware from triggering "sleep mode"
>>> while the sleep/wake TCS is being borrowed for an active-only
>>> transaction.  If we're sitting in rpmh_write() and sleeping in
>>> wait_for_completion_timeout() couldn't the system go idle and trigger
>>> sleep mode?  In OSI-mode (with last man down) you'll always have a
>>> rpmh_flush() called by the last man down so you know you can make sure
>>> you're in a consistent state (one final flush and no more active-only
>> transactions will happen).  Without last man down you have to assume
>>> it can happen at any time don't you?
>>>
>>> ...so I guess I'll go back to asserting that zero-active-TCS is
>>> incompatible with non-OSI unless you have some way to prevent the
>>> sleep mode from being triggered while you've borrowed the wake TCS.
>> i had change for this in v4 to handle same.
>>
>> Link: https://patchwork.kernel.org/patch/11366205/
>>
>> + /*
>> + * RPMh domain can not be powered off when there is pending ACK for
>> + * ACTIVE_TCS request. Exit when controller is busy.
>> + */
>>
>> before calling rpmh_flush() we check ctrlr is idle (ensuring
>>
>> tcs_is_free() check passes)  this will ensure that
>>
>> previous active transaction is completed before going ahead.
>>
>> i will add this check in v14.
>>
>> since this curretntly check for ACTIVE TCS only, i will update it to check the repurposed "WAKE TCS" is also free.
> The difficulty isn't in adding a simple check, it's in adding a check
> that is race free and handles locking properly.  Specifically looking
> at your the v4 you pointed to, I see things like this:
>
>   if (!rpmh_rsc_ctrlr_is_idle(drv))
>     return -EBUSY;
>   return rpmh_flush(&drv->client);
>
> The rpmh_rsc_ctrlr_is_idle() grabs a spin lock implying that there
> could be other people acting on RPMh at the same time (otherwise, why
> do you even need locks?).  ...but when it returns the lock is
> released.  Once the lock is dropped then other clients are free to
> start using RPMH because nothing prevents them.  ...then you go ahead
> and start flushing.
>
> Said another way: due to the way you've structured locking in that
> patch rpmh_rsc_ctrlr_is_idle() is inherently dangerous because it
> returns an instantaneous state that may well have changed between the
> spin_unlock_irqrestore() at the end of the function and when the
> function returns.
>
> You could, of course, fix this by requiring that the caller hold the
> 'drv->lock' for both the calls to rpmh_rsc_ctrlr_is_idle() and
> rpmh_flush() (ignoring the fact the drv->lock is nominally part of
> rpmh-rsc.c and not rpmh.c).  Now it would be safe from the problem I
> described.  ...but now you get into a new problem.  If you ever hold
> two locks you always need to make sure you grab them in the same order
> any time you grab them both.  ...but tcs_write() we first grab a
> tcs_lock and _then_ drv->lock.  That means the "fix" of holding
> drv->lock for both the calls to rpmh_rsc_ctrlr_is_idle() and
> rpmh_flush() won't work because rpmh_flush() will need to grab a
> tcs_lock.  Possibly we could make this work by eliminating the "tcs
> lock" and just having the one big "drv->lock" protect everything (or
> introducing a new "super lock" making the other two meaningless).  It
> would certainly be easier to reason about...
Thanks Doug.

Agree, a simple check won't help here.

From above discussions, summarizing out 3 items that gets impacted when using rpmh_start/end_transaction().

1. rpmh_write_async() becomes of little use since drivers may need to wait for rpmh_flush() to finish
if caches becomes dirty in between.
2. It creates more pressure on WAKE TCS when there is no dedicated ACTIVE TCS. Transactions are delayed
if rpmh_flush() is in progress holding the locks and new request comes in to send Active only data.
3. rpmh_rsc_ctrlr_is_idle() needs locking if ANY cpu can be calling this, this may require reordering of
locks / increase the time for which locks are held during rpmh transactions. On downstream variant we don't
have locking in this since in OSI, only last CPU is invoking it and it is the only one invoking this function.

Given above impact this approach seem not so simple as i though of earlier. i have alternate solution which
uses cpu_pm_notifications() and invokes rpmh_flush() for non-OSI targets. This approach should not impact
above 3 items.

I will soon post out v14 with this, testing in progress.

>
> NOTE: the only way I'm able to reason about all the above things is
> because I spent all the time to document what rpmh-rsc is doing and
> what assumptions the various functions had [1].  It'd be great if that
> could get a review.
Sure.
>
>
>>> This whole "no active TCS" is really quite a mess.  Given how broken
>>> it seems to me it almost feels like we should remove "no active TCS"
>>> from the driver for now and then re-add it in a later patch when we
>>> can really validate everything.  I tried addressing this in my
>>> rpmh-rsc cleanup series and every time I thought I had a good solution
>>> I could find another way to break it.
>>>
>>> Do you actually have the "no active TCS" case working on the current
>>> code, or does it only work on some downstream variant of the driver?
>>>
>>> It works fine on downstream variant. Some checks are still needed like above from v4
>>>
>>> since we do rpmh_flush() immediatly for dirty caches now.
> OK.  So I take it you haven't tested the "zero active" case with the
> upstream code?  In theory it should be easy to test, right?  Just hack
> the driver to pretend there are zero active TCSs?

No, it won't work out this way. if we want to test with zero active case, need to pick up [2].

[2] also need follow up fixes to work. This change is also in my to do
list to get merged. I will include a change in v14 series at the end, it can help test this series
for zero active tcs as well.

Note that it doesn't have any dependency with this series and current series can get merged
without [2].
>
> Which SoCs in specific need the zero active TCSs?  We are spending a
> lot of time talking about this and reviewing the code with this in
> mind.  It adds a lot of complexity to the driver.  If nothing under
> active development needs it I think we should do ourselves a favor and
> remove it for now, then add it back later.  Otherwise this whole
> process is just going to take a lot more time.
>
There are multiple SoCs having zero active TCSes in downstream code. So we can not remove it.

As i said above we need [2] plus some fixes to have zero active TCS working fine on upstream driver.

Thanks,
Maulik

>>> I'm not saying we should limit the total number of requests to 1000.
>>> I'm saying that if there are 1000 active clients then that's a
>>> problem.  Right now there are something like 4 clients.  It doesn't
>>> matter how fast those clients are sending, active_clients will only be
>>> at most 4 and that would only be if they were all running their code
>>> at the exact same time.
>>>
>>> I want to be able to quickly detect this type of bug:
>>>
>>> start_transaction()
>>> ret = rpmh_write()
>>> if (ret)
>>>   return ret;
>>> ret = rpmh_write()
>>> end_transaction()
>>> return ret;
>>>
>>> ...in other words: someone has a code path where start_transaction()
>>> is called but never end_transaction().  I'm proposing that if we ever
>>> see the ridiculous value of 1000 active clients the only way it could
>>> happen is if one of the clients started more times than they ended.
>>>
>>>
>>> I guess maybe by the time there were 1000 it would be too late,
>>> though, because we'd have skipped A LOT of flushes by then?  Maybe
>>> instead we should add something where if RPMH is "ditry" for more than
>>> a certain amount of time we put a warning?
>> IMO, we should not add any such warning with any number.
>> There are only 4 clients and unlikely to have any new ones. those 4 we should be able to ensure
>> that they invoke end_transaction(), if they have already done start_transaction().beside,
>> Function description also says that "this must be ended by invoking rpmh_end_transaction()"
>> i am ok to also add  saying that "rpmh do not check this, so its caller's responsibility to
>> end it"
> I don't agree but I won't argue further.  If you want to leave out the
> WARN() then so be it.
>
> -Doug
>
> [1] https://lore.kernel.org/r/20200311161104.RFT.v2.5.I52653eb85d7dc8981ee0dafcd0b6cc0f273e9425@changeid

[2] https://patchwork.kernel.org/patch/10818129/
Doug Anderson March 26, 2020, 6:08 p.m. UTC | #8
Hi,

On Wed, Mar 25, 2020 at 10:16 AM Maulik Shah <mkshah@codeaurora.org> wrote:
>
> Hi,
>
> On 3/12/2020 8:41 PM, Doug Anderson wrote:
> > Hi,
> >
> > Quoting below may look funny since you replied with HTML mail and all
> > old quoting was lost.  :(  I tried my best.
> >
> > On Thu, Mar 12, 2020 at 3:32 AM Maulik Shah <mkshah@codeaurora.org> wrote:
> >>> Specifically I wouldn't trust all callers of rpmh_write() /
> >>> rpmh_write_async() to never send the same data.  If it was just an
> >>> speed/power optimization then sure you could trust them, but this is
> >>> for correctness.
> >> yes we should trust callers not to send duplicate data.
> > I guess we'll see how this works out.  Since this only affects the
> > "zero active-only" case and I'm pretty sure that case has more
> > important issues, I'm OK w/ ignoring for now.
> >
> >
> >>> Hrmm, thinking about this again, though, I'm still not convinced I
> >>> understand what prevents the firmware from triggering "sleep mode"
> >>> while the sleep/wake TCS is being borrowed for an active-only
> >>> transaction.  If we're sitting in rpmh_write() and sleeping in
> >>> wait_for_completion_timeout() couldn't the system go idle and trigger
> >>> sleep mode?  In OSI-mode (with last man down) you'll always have a
> >>> rpmh_flush() called by the last man down so you know you can make sure
> >>> you're in a consistent state (one final flush and no more active-only
> >> transactions will happen).  Without last man down you have to assume
> >>> it can happen at any time don't you?
> >>>
> >>> ...so I guess I'll go back to asserting that zero-active-TCS is
> >>> incompatible with non-OSI unless you have some way to prevent the
> >>> sleep mode from being triggered while you've borrowed the wake TCS.
> >> i had change for this in v4 to handle same.
> >>
> >> Link: https://patchwork.kernel.org/patch/11366205/
> >>
> >> + /*
> >> + * RPMh domain can not be powered off when there is pending ACK for
> >> + * ACTIVE_TCS request. Exit when controller is busy.
> >> + */
> >>
> >> before calling rpmh_flush() we check ctrlr is idle (ensuring
> >>
> >> tcs_is_free() check passes)  this will ensure that
> >>
> >> previous active transaction is completed before going ahead.
> >>
> >> i will add this check in v14.
> >>
> >> since this curretntly check for ACTIVE TCS only, i will update it to check the repurposed "WAKE TCS" is also free.
> > The difficulty isn't in adding a simple check, it's in adding a check
> > that is race free and handles locking properly.  Specifically looking
> > at your the v4 you pointed to, I see things like this:
> >
> >   if (!rpmh_rsc_ctrlr_is_idle(drv))
> >     return -EBUSY;
> >   return rpmh_flush(&drv->client);
> >
> > The rpmh_rsc_ctrlr_is_idle() grabs a spin lock implying that there
> > could be other people acting on RPMh at the same time (otherwise, why
> > do you even need locks?).  ...but when it returns the lock is
> > released.  Once the lock is dropped then other clients are free to
> > start using RPMH because nothing prevents them.  ...then you go ahead
> > and start flushing.
> >
> > Said another way: due to the way you've structured locking in that
> > patch rpmh_rsc_ctrlr_is_idle() is inherently dangerous because it
> > returns an instantaneous state that may well have changed between the
> > spin_unlock_irqrestore() at the end of the function and when the
> > function returns.
> >
> > You could, of course, fix this by requiring that the caller hold the
> > 'drv->lock' for both the calls to rpmh_rsc_ctrlr_is_idle() and
> > rpmh_flush() (ignoring the fact the drv->lock is nominally part of
> > rpmh-rsc.c and not rpmh.c).  Now it would be safe from the problem I
> > described.  ...but now you get into a new problem.  If you ever hold
> > two locks you always need to make sure you grab them in the same order
> > any time you grab them both.  ...but tcs_write() we first grab a
> > tcs_lock and _then_ drv->lock.  That means the "fix" of holding
> > drv->lock for both the calls to rpmh_rsc_ctrlr_is_idle() and
> > rpmh_flush() won't work because rpmh_flush() will need to grab a
> > tcs_lock.  Possibly we could make this work by eliminating the "tcs
> > lock" and just having the one big "drv->lock" protect everything (or
> > introducing a new "super lock" making the other two meaningless).  It
> > would certainly be easier to reason about...
> Thanks Doug.
>
> Agree, a simple check won't help here.
>
> From above discussions, summarizing out 3 items that gets impacted when using rpmh_start/end_transaction().
>
> 1. rpmh_write_async() becomes of little use since drivers may need to wait for rpmh_flush() to finish
> if caches becomes dirty in between.

I think this is really just a problem if there are no dedicated ACTIVE
TCS.  I think rpmh_flush() is pretty quick normally because all it's
doing is writing to register space, not waiting for any transactions
to finish.  The reason it's bad if there are no dedicated ACTIVE TCS
is that now we have to block waiting for the active transaction to
finish.


> 2. It creates more pressure on WAKE TCS when there is no dedicated ACTIVE TCS. Transactions are delayed
> if rpmh_flush() is in progress holding the locks and new request comes in to send Active only data.
> 3. rpmh_rsc_ctrlr_is_idle() needs locking if ANY cpu can be calling this, this may require reordering of
> locks / increase the time for which locks are held during rpmh transactions. On downstream variant we don't
> have locking in this since in OSI, only last CPU is invoking it and it is the only one invoking this function.
>
> Given above impact this approach seem not so simple as i though of earlier. i have alternate solution which
> uses cpu_pm_notifications() and invokes rpmh_flush() for non-OSI targets. This approach should not impact
> above 3 items.

Grepping for "cpu_pm_notifications" finds nothing, but I think you
mean you're going to register for notifications with
register_pm_notifier().

OK.  I guess I'm a bit confused here, though.  Maybe you can help
clear up my understanding.  I thought that one of the things you were
trying to solve with all the "last man down" type solutions was to
handle when RPMH wanted to transition into its sleep mode without a
full system suspend.  I thought that the RPMH sleep mode ran sometimes
when all the CPUs were idle and we were pretty sure they were going to
be idle for a while.

If this whole time all you've needed is to run at suspend time then it
feels like we could have avoided a whole lot of complexity.  ...but
again, maybe I'm just misunderstanding.


> I will soon post out v14 with this, testing in progress.
>
> >
> > NOTE: the only way I'm able to reason about all the above things is
> > because I spent all the time to document what rpmh-rsc is doing and
> > what assumptions the various functions had [1].  It'd be great if that
> > could get a review.
> Sure.
> >
> >
> >>> This whole "no active TCS" is really quite a mess.  Given how broken
> >>> it seems to me it almost feels like we should remove "no active TCS"
> >>> from the driver for now and then re-add it in a later patch when we
> >>> can really validate everything.  I tried addressing this in my
> >>> rpmh-rsc cleanup series and every time I thought I had a good solution
> >>> I could find another way to break it.
> >>>
> >>> Do you actually have the "no active TCS" case working on the current
> >>> code, or does it only work on some downstream variant of the driver?
> >>>
> >>> It works fine on downstream variant. Some checks are still needed like above from v4
> >>>
> >>> since we do rpmh_flush() immediatly for dirty caches now.
> > OK.  So I take it you haven't tested the "zero active" case with the
> > upstream code?  In theory it should be easy to test, right?  Just hack
> > the driver to pretend there are zero active TCSs?
>
> No, it won't work out this way. if we want to test with zero active case, need to pick up [2].
>
> [2] also need follow up fixes to work. This change is also in my to do
> list to get merged. I will include a change in v14 series at the end, it can help test this series
> for zero active tcs as well.

I would just provide a pointer to it in the description.  If it was
already there and I missed it, then sorry.  :(


> Note that it doesn't have any dependency with this series and current series can get merged
> without [2].

Ah, this was a patch I wasn't aware of.  I haven't had time to go scan
for patches that weren't pointed in my direction.  I'll go review it
now.  When I briefly looked at trying to solve this problem myself I
seem to remember it being harder to get all the locking right / races
fixed, so I'm a little surprised that patch is so short...  Maybe I
was overthinking...


> > Which SoCs in specific need the zero active TCSs?  We are spending a
> > lot of time talking about this and reviewing the code with this in
> > mind.  It adds a lot of complexity to the driver.  If nothing under
> > active development needs it I think we should do ourselves a favor and
> > remove it for now, then add it back later.  Otherwise this whole
> > process is just going to take a lot more time.
> >
> There are multiple SoCs having zero active TCSes in downstream code. So we can not remove it.
>
> As i said above we need [2] plus some fixes to have zero active TCS working fine on upstream driver.

If you have it working then no need to remove it.  ...but without that
patch it was clearly not working and it was adding a lot of complexity
to handle it.  In fact, this flushing patch series would have likely
been easy to get finalized / landed if we hadn't needed to deal with
the zero active TCS case.  It still feels like an option to say that
the "zero active TCS" case is only supported when you have OSI mode
unless you know of instances where we need that.


Hrm, I see you just posted v14 while I was writing this.  I guess I'll
go check that out now.  Maybe it will answer some of the questions I
had.


-Doug
diff mbox series

Patch

diff --git a/drivers/soc/qcom/rpmh-internal.h b/drivers/soc/qcom/rpmh-internal.h
index 6eec32b..d36be3d 100644
--- a/drivers/soc/qcom/rpmh-internal.h
+++ b/drivers/soc/qcom/rpmh-internal.h
@@ -70,13 +70,17 @@  struct rpmh_request {
  *
  * @cache: the list of cached requests
  * @cache_lock: synchronize access to the cache data
+ * @active_clients: count of rpmh transaction in progress
  * @dirty: was the cache updated since flush
+ * @flush_dirty: if the dirty cache need immediate flush
  * @batch_cache: Cache sleep and wake requests sent as batch
  */
 struct rpmh_ctrlr {
 	struct list_head cache;
 	spinlock_t cache_lock;
+	u32 active_clients;
 	bool dirty;
+	bool flush_dirty;
 	struct list_head batch_cache;
 };
 
diff --git a/drivers/soc/qcom/rpmh-rsc.c b/drivers/soc/qcom/rpmh-rsc.c
index e278fc1..b6391e1 100644
--- a/drivers/soc/qcom/rpmh-rsc.c
+++ b/drivers/soc/qcom/rpmh-rsc.c
@@ -61,6 +61,8 @@ 
 #define CMD_STATUS_ISSUED		BIT(8)
 #define CMD_STATUS_COMPL		BIT(16)
 
+#define FLUSH_DIRTY			1
+
 static u32 read_tcs_reg(struct rsc_drv *drv, int reg, int tcs_id, int cmd_id)
 {
 	return readl_relaxed(drv->tcs_base + reg + RSC_DRV_TCS_OFFSET * tcs_id +
@@ -670,13 +672,15 @@  static int rpmh_rsc_probe(struct platform_device *pdev)
 	INIT_LIST_HEAD(&drv->client.cache);
 	INIT_LIST_HEAD(&drv->client.batch_cache);
 
+	drv->client.flush_dirty = device_get_match_data(&pdev->dev);
+
 	dev_set_drvdata(&pdev->dev, drv);
 
 	return devm_of_platform_populate(&pdev->dev);
 }
 
 static const struct of_device_id rpmh_drv_match[] = {
-	{ .compatible = "qcom,rpmh-rsc", },
+	{ .compatible = "qcom,rpmh-rsc", .data = (void *)FLUSH_DIRTY },
 	{ }
 };
 
diff --git a/drivers/soc/qcom/rpmh.c b/drivers/soc/qcom/rpmh.c
index 5bed8f4..9d40209 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;
 }
@@ -433,16 +430,63 @@  static int send_single(struct rpmh_ctrlr *ctrlr, enum rpmh_state state,
 }
 
 /**
+ * rpmh_start_transaction: Indicates start of rpmh transactions, this
+ * must be ended by invoking rpmh_end_transaction().
+ *
+ * @dev: the device making the request
+ */
+void rpmh_start_transaction(const struct device *dev)
+{
+	struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr(dev);
+	unsigned long flags;
+
+	if (!ctrlr->flush_dirty)
+		return;
+
+	spin_lock_irqsave(&ctrlr->cache_lock, flags);
+	ctrlr->active_clients++;
+	spin_unlock_irqrestore(&ctrlr->cache_lock, flags);
+}
+EXPORT_SYMBOL(rpmh_start_transaction);
+
+/**
+ * rpmh_end_transaction: Indicates end of rpmh transactions. All dirty data
+ * in cache can be flushed immediately when ctrlr->flush_dirty is set
+ *
+ * @dev: the device making the request
+ *
+ * Return: 0 on success, error number otherwise.
+ */
+int rpmh_end_transaction(const struct device *dev)
+{
+	struct rpmh_ctrlr *ctrlr = get_rpmh_ctrlr(dev);
+	unsigned long flags;
+	int ret = 0;
+
+	if (!ctrlr->flush_dirty)
+		return ret;
+
+	spin_lock_irqsave(&ctrlr->cache_lock, flags);
+
+	ctrlr->active_clients--;
+	if (ctrlr->dirty && !ctrlr->active_clients)
+		ret = rpmh_flush(ctrlr);
+
+	spin_unlock_irqrestore(&ctrlr->cache_lock, flags);
+
+	return ret;
+}
+EXPORT_SYMBOL(rpmh_end_transaction);
+
+/**
  * rpmh_flush: Flushes the buffered active and sleep sets to TCS
  *
  * @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)
 {
@@ -464,10 +508,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",
diff --git a/include/soc/qcom/rpmh.h b/include/soc/qcom/rpmh.h
index f9ec353..85e1ab2 100644
--- a/include/soc/qcom/rpmh.h
+++ b/include/soc/qcom/rpmh.h
@@ -22,6 +22,10 @@  int rpmh_write_batch(const struct device *dev, enum rpmh_state state,
 
 int rpmh_invalidate(const struct device *dev);
 
+void rpmh_start_transaction(const struct device *dev);
+
+int rpmh_end_transaction(const struct device *dev);
+
 #else
 
 static inline int rpmh_write(const struct device *dev, enum rpmh_state state,
@@ -41,6 +45,12 @@  static inline int rpmh_write_batch(const struct device *dev,
 static inline int rpmh_invalidate(const struct device *dev)
 { return -ENODEV; }
 
+void rpmh_start_transaction(const struct device *dev)
+{ return -ENODEV; }
+
+int rpmh_end_transaction(const struct device *dev)
+{ return -ENODEV; }
+
 #endif /* CONFIG_QCOM_RPMH */
 
 #endif /* __SOC_QCOM_RPMH_H__ */