diff mbox series

[RESEND,5/5] clk: ccf: call clock provided ops directly for endisable()

Message ID 20230818-clk-fix-v1-5-49ec18f820bf@outlook.com
State Changes Requested
Delegated to: Sean Anderson
Headers show
Series clk: A few bugfixes/enhancements for CCF | expand

Commit Message

Yang Xiwen via B4 Relay Aug. 17, 2023, 5:04 p.m. UTC
From: Yang Xiwen <forbidden405@outlook.com>

Calling into CCF framework will cause a clock being enabled twice
instead of once (clk->enable_count becomes 2 rather than 1), thus making
it hard to disable (needs to call clk_disable() twice).
Fix that by calling clock provided ops directly.

Signed-off-by: Yang Xiwen <forbidden405@outlook.com>
---
 drivers/clk/clk.c | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

Comments

Sean Anderson Nov. 1, 2023, 6:19 p.m. UTC | #1
On 8/17/23 13:04, Yang Xiwen via B4 Relay wrote:
> From: Yang Xiwen <forbidden405@outlook.com>
> 
> Calling into CCF framework will cause a clock being enabled twice
> instead of once (clk->enable_count becomes 2 rather than 1), thus making
> it hard to disable (needs to call clk_disable() twice).
> Fix that by calling clock provided ops directly.

Can you describe this scenario more? From what I can tell, clk_enable doesn't
increment enable_count for CCF clocks.

--Sean

> Signed-off-by: Yang Xiwen <forbidden405@outlook.com>
> ---
>   drivers/clk/clk.c | 12 +++++++++++-
>   1 file changed, 11 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
> index a38daaac0c..00d082c46f 100644
> --- a/drivers/clk/clk.c
> +++ b/drivers/clk/clk.c
> @@ -14,6 +14,7 @@
>   #include <dm/uclass.h>
>   #include <dm/lists.h>
>   #include <dm/device-internal.h>
> +#include <linux/clk-provider.h>
>   
>   int clk_register(struct clk *clk, const char *drv_name,
>   		 const char *name, const char *parent_name)
> @@ -115,11 +116,20 @@ int ccf_clk_set_parent(struct clk *clk, struct clk *parent)
>   static int ccf_clk_endisable(struct clk *clk, bool enable)
>   {
>   	struct clk *c;
> +	const struct clk_ops *ops;
>   	int err = clk_get_by_id(clk->id, &c);
>   
>   	if (err)
>   		return err;
> -	return enable ? clk_enable(c) : clk_disable(c);
> +	else
> +		ops = clk_dev_ops(c->dev);
> +
> +	if (enable && ops->enable)
> +		return ops->enable(c);
> +	else if (!enable && ops->disable)
> +		return ops->disable(c);
> +
> +	return -ENOSYS;
>   }
>   
>   int ccf_clk_enable(struct clk *clk)
>
Yang Xiwen Nov. 1, 2023, 6:50 p.m. UTC | #2
On 11/2/2023 2:19 AM, Sean Anderson wrote:
> On 8/17/23 13:04, Yang Xiwen via B4 Relay wrote:
>> From: Yang Xiwen <forbidden405@outlook.com>
>>
>> Calling into CCF framework will cause a clock being enabled twice
>> instead of once (clk->enable_count becomes 2 rather than 1), thus making
>> it hard to disable (needs to call clk_disable() twice).
>> Fix that by calling clock provided ops directly.
> 
> Can you describe this scenario more? From what I can tell, clk_enable
> doesn't
> increment enable_count for CCF clocks.
> 
Well, it's hard to describe clearly. But I can only tell this patch
fixed the issue when i was trying to write an Ethernet driver[1] which
calls clk_disable() and expects the clock to be disabled after that.
Also I found that CCF driver does not have a corresponding test file. I
will try to write a test for that in next release.
> --Sean
> 
>> Signed-off-by: Yang Xiwen <forbidden405@outlook.com>
>> ---
>>   drivers/clk/clk.c | 12 +++++++++++-
>>   1 file changed, 11 insertions(+), 1 deletion(-)
>>
>> diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
>> index a38daaac0c..00d082c46f 100644
>> --- a/drivers/clk/clk.c
>> +++ b/drivers/clk/clk.c
>> @@ -14,6 +14,7 @@
>>   #include <dm/uclass.h>
>>   #include <dm/lists.h>
>>   #include <dm/device-internal.h>
>> +#include <linux/clk-provider.h>
>>     int clk_register(struct clk *clk, const char *drv_name,
>>            const char *name, const char *parent_name)
>> @@ -115,11 +116,20 @@ int ccf_clk_set_parent(struct clk *clk, struct
>> clk *parent)
>>   static int ccf_clk_endisable(struct clk *clk, bool enable)
>>   {
>>       struct clk *c;
>> +    const struct clk_ops *ops;
>>       int err = clk_get_by_id(clk->id, &c);
>>         if (err)
>>           return err;
>> -    return enable ? clk_enable(c) : clk_disable(c);
>> +    else
>> +        ops = clk_dev_ops(c->dev);
>> +
>> +    if (enable && ops->enable)
>> +        return ops->enable(c);
>> +    else if (!enable && ops->disable)
>> +        return ops->disable(c);
>> +
>> +    return -ENOSYS;
>>   }
>>     int ccf_clk_enable(struct clk *clk)
>>
> 
[1]
https://lore.kernel.org/all/20230814-wip-hisi_femac-trunk-v2-0-1e29f400585d@outlook.com/
Yang Xiwen Nov. 1, 2023, 8:33 p.m. UTC | #3
On 11/2/2023 2:50 AM, Yang Xiwen wrote:
> On 11/2/2023 2:19 AM, Sean Anderson wrote:
>> On 8/17/23 13:04, Yang Xiwen via B4 Relay wrote:
>>> From: Yang Xiwen <forbidden405@outlook.com>
>>>
>>> Calling into CCF framework will cause a clock being enabled twice
>>> instead of once (clk->enable_count becomes 2 rather than 1), thus making
>>> it hard to disable (needs to call clk_disable() twice).
>>> Fix that by calling clock provided ops directly.
>>
>> Can you describe this scenario more? From what I can tell, clk_enable
>> doesn't
>> increment enable_count for CCF clocks.
>>
> Well, it's hard to describe clearly. But I can only tell this patch
> fixed the issue when i was trying to write an Ethernet driver[1] which
> calls clk_disable() and expects the clock to be disabled after that.
> Also I found that CCF driver does not have a corresponding test file. I
> will try to write a test for that in next release.
Okay, fine. I read the source again and let me try to explain the whole
thing to you briefly. Let's see what happens when we are calling
clk_enable(gate).

The source of clk.c is listed below and labeled for clarity:

```
1	if (CONFIG_IS_ENABLED(CLK_CCF)) {
2		/* Take id 0 as a non-valid clk, such as dummy */
3		if (clk->id && !clk_get_by_id(clk->id, &clkp)) {
4			if (clkp->enable_count) {
5				clkp->enable_count++;
6				return 0;
7			}
8			if (clkp->dev->parent &&
9			    device_get_uclass_id(clkp->dev->parent) == UCLASS_CLK) {
10				ret = clk_enable(dev_get_clk_ptr(clkp->dev->parent));
11				if (ret) {
12					printf("Enable %s failed\n",
13					       clkp->dev->parent->name);
14					return ret;
15				}
16			}
17		}
18
19		if (ops->enable) {
20			ret = ops->enable(clk);
21			if (ret) {
22				printf("Enable %s failed\n", clk->dev->name);
23				return ret;
24			}
25		}
26		if (clkp)
27			clkp->enable_count++;
28	} else {
29		if (!ops->enable)
30			return -ENOSYS;
31		return ops->enable(clk);
```

The following steps are executed:

1. Actually, a "fake" clk is passed to clk_enable() and only clk->id is
valid. The actual clk is "clkp";
2. Initially, we runs till `ret = ops->enable(clk)`(line 20), i.e.
ccf_clk_enable(clk);
3. Thankfully, ccf_clk_enable() calls clk_get_by_id() to get the real
clk and call clk_enable(clkp) again so we won't have an endless loop here.
4. So ops->enable(clk) actually equals to clk_enable(clkp). It's obvious
that there is a `clkp->enable_count++` inside the nested function call
since it's still 0. Now it becomes 1;
5. The nested clk_enable(clkp) now returns to the outer clk_enable(clk);
6. Unfortunately, there is a `if (clkp) clkp->enable_count++;`(line 26)
afterwards. Now it becomes 2.
7. Finally, we got a clk being enabled twice. "clkp->enable_count" is 2 now.

Obviously it's not the intended behavior. We can either fix clk_enable()
or ccf_clk_endisable() to resolve this. But I choose to touch
ccf_clk_endisable() since it's less commonly used.
>> --Sean
>>
>>> Signed-off-by: Yang Xiwen <forbidden405@outlook.com>
>>> ---
>>>   drivers/clk/clk.c | 12 +++++++++++-
>>>   1 file changed, 11 insertions(+), 1 deletion(-)
>>>
>>> diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
>>> index a38daaac0c..00d082c46f 100644
>>> --- a/drivers/clk/clk.c
>>> +++ b/drivers/clk/clk.c
>>> @@ -14,6 +14,7 @@
>>>   #include <dm/uclass.h>
>>>   #include <dm/lists.h>
>>>   #include <dm/device-internal.h>
>>> +#include <linux/clk-provider.h>
>>>     int clk_register(struct clk *clk, const char *drv_name,
>>>            const char *name, const char *parent_name)
>>> @@ -115,11 +116,20 @@ int ccf_clk_set_parent(struct clk *clk, struct
>>> clk *parent)
>>>   static int ccf_clk_endisable(struct clk *clk, bool enable)
>>>   {
>>>       struct clk *c;
>>> +    const struct clk_ops *ops;
>>>       int err = clk_get_by_id(clk->id, &c);
>>>         if (err)
>>>           return err;
>>> -    return enable ? clk_enable(c) : clk_disable(c);
>>> +    else
>>> +        ops = clk_dev_ops(c->dev);
>>> +
>>> +    if (enable && ops->enable)
>>> +        return ops->enable(c);
>>> +    else if (!enable && ops->disable)
>>> +        return ops->disable(c);
>>> +
>>> +    return -ENOSYS;
>>>   }
>>>     int ccf_clk_enable(struct clk *clk)
>>>
>>
> [1]
> https://lore.kernel.org/all/20230814-wip-hisi_femac-trunk-v2-0-1e29f400585d@outlook.com/
>
Sean Anderson Nov. 4, 2023, 3:35 p.m. UTC | #4
On 11/1/23 16:33, Yang Xiwen wrote:
> On 11/2/2023 2:50 AM, Yang Xiwen wrote:
>> On 11/2/2023 2:19 AM, Sean Anderson wrote:
>>> On 8/17/23 13:04, Yang Xiwen via B4 Relay wrote:
>>>> From: Yang Xiwen <forbidden405@outlook.com>
>>>>
>>>> Calling into CCF framework will cause a clock being enabled twice
>>>> instead of once (clk->enable_count becomes 2 rather than 1), thus making
>>>> it hard to disable (needs to call clk_disable() twice).
>>>> Fix that by calling clock provided ops directly.
>>>
>>> Can you describe this scenario more? From what I can tell, clk_enable
>>> doesn't
>>> increment enable_count for CCF clocks.
>>>
>> Well, it's hard to describe clearly. But I can only tell this patch
>> fixed the issue when i was trying to write an Ethernet driver[1] which
>> calls clk_disable() and expects the clock to be disabled after that.
>> Also I found that CCF driver does not have a corresponding test file. I
>> will try to write a test for that in next release.
> Okay, fine. I read the source again and let me try to explain the whole
> thing to you briefly. Let's see what happens when we are calling
> clk_enable(gate).
> 
> The source of clk.c is listed below and labeled for clarity:
> 
> ```
> 1	if (CONFIG_IS_ENABLED(CLK_CCF)) {
> 2		/* Take id 0 as a non-valid clk, such as dummy */
> 3		if (clk->id && !clk_get_by_id(clk->id, &clkp)) {
> 4			if (clkp->enable_count) {
> 5				clkp->enable_count++;
> 6				return 0;
> 7			}
> 8			if (clkp->dev->parent &&
> 9			    device_get_uclass_id(clkp->dev->parent) == UCLASS_CLK) {
> 10				ret = clk_enable(dev_get_clk_ptr(clkp->dev->parent));
> 11				if (ret) {
> 12					printf("Enable %s failed\n",
> 13					       clkp->dev->parent->name);
> 14					return ret;
> 15				}
> 16			}
> 17		}
> 18
> 19		if (ops->enable) {
> 20			ret = ops->enable(clk);
> 21			if (ret) {
> 22				printf("Enable %s failed\n", clk->dev->name);
> 23				return ret;
> 24			}
> 25		}
> 26		if (clkp)
> 27			clkp->enable_count++;
> 28	} else {
> 29		if (!ops->enable)
> 30			return -ENOSYS;
> 31		return ops->enable(clk);
> ```
> 
> The following steps are executed:
> 
> 1. Actually, a "fake" clk is passed to clk_enable() and only clk->id is
> valid. The actual clk is "clkp";
> 2. Initially, we runs till `ret = ops->enable(clk)`(line 20), i.e.
> ccf_clk_enable(clk);
> 3. Thankfully, ccf_clk_enable() calls clk_get_by_id() to get the real
> clk and call clk_enable(clkp) again so we won't have an endless loop here.
> 4. So ops->enable(clk) actually equals to clk_enable(clkp). It's obvious
> that there is a `clkp->enable_count++` inside the nested function call
> since it's still 0. Now it becomes 1;
> 5. The nested clk_enable(clkp) now returns to the outer clk_enable(clk);
> 6. Unfortunately, there is a `if (clkp) clkp->enable_count++;`(line 26)
> afterwards. Now it becomes 2.
> 7. Finally, we got a clk being enabled twice. "clkp->enable_count" is 2 now.

OK, thank you for writing this up; it is clearer now. Please include this in
your commit message.

> Obviously it's not the intended behavior. We can either fix clk_enable()
> or ccf_clk_endisable() to resolve this. But I choose to touch
> ccf_clk_endisable() since it's less commonly used.

Hm, what if we added something like clk_raw_enable, which just did

> 19		if (ops->enable) {
> 20			ret = ops->enable(clk);
> 21			if (ret) {
> 22				printf("Enable %s failed\n", clk->dev->name);
> 23				return ret;
> 24			}
> 25		}

and the same thing for disable.

--Sean
diff mbox series

Patch

diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
index a38daaac0c..00d082c46f 100644
--- a/drivers/clk/clk.c
+++ b/drivers/clk/clk.c
@@ -14,6 +14,7 @@ 
 #include <dm/uclass.h>
 #include <dm/lists.h>
 #include <dm/device-internal.h>
+#include <linux/clk-provider.h>
 
 int clk_register(struct clk *clk, const char *drv_name,
 		 const char *name, const char *parent_name)
@@ -115,11 +116,20 @@  int ccf_clk_set_parent(struct clk *clk, struct clk *parent)
 static int ccf_clk_endisable(struct clk *clk, bool enable)
 {
 	struct clk *c;
+	const struct clk_ops *ops;
 	int err = clk_get_by_id(clk->id, &c);
 
 	if (err)
 		return err;
-	return enable ? clk_enable(c) : clk_disable(c);
+	else
+		ops = clk_dev_ops(c->dev);
+
+	if (enable && ops->enable)
+		return ops->enable(c);
+	else if (!enable && ops->disable)
+		return ops->disable(c);
+
+	return -ENOSYS;
 }
 
 int ccf_clk_enable(struct clk *clk)