Skip to contentSkip to conversation

cpufreq: schedutil: Don't ignore limit changes when util is unchanged

PATCH In review Sultan Alsawaf 3 participants 11 messages Apr 10, 2025 – Apr 11, 2025

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

PATCH 1/1 cpufreq: schedutil: Don't ignore limit changes when util is unchanged

From: Sultan Alsawaf <[email protected]>

When utilization is unchanged, a policy limits update is ignored unless CPUFREQ_NEED_UPDATE_LIMITS is set. This occurs because limits_changed depends on the old broken behavior of need_freq_update to trigger a call into cpufreq_driver_resolve_freq() to evaluate the changed policy limits.

After fixing need_freq_update, limit changes are ignored without CPUFREQ_NEED_UPDATE_LIMITS, at least until utilization changes enough to make map_util_freq() return something different.

Fix the ignored limit changes by preserving the value of limits_changed until get_next_freq() is called, so limits_changed can trigger a call to cpufreq_driver_resolve_freq().

Fixes 8e461a1cb43d6 ("cpufreq: schedutil: Fix superfluous updates caused by need_freq_update")
---
 kernel/sched/cpufreq_schedutil.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
kernel/sched/cpufreq_schedutil.c +3 -2
index 1a19d69b91ed3..f37b999854d52 100644
@@ -82,7 +82,6 @@ static bool sugov_should_update_freq(struct sugov_policy *sg_policy, u64 time)
 		return false;
 
 	if (unlikely(sg_policy->limits_changed)) {
-		sg_policy->limits_changed = false;
 		sg_policy->need_freq_update = cpufreq_driver_test_flags(CPUFREQ_NEED_UPDATE_LIMITS);
 		return true;
 	}
@@ -171,9 +170,11 @@ static unsigned int get_next_freq(struct sugov_policy *sg_policy,
 	freq = get_capacity_ref_freq(policy);
 	freq = map_util_freq(util, freq, max);
 
-	if (freq == sg_policy->cached_raw_freq && !sg_policy->need_freq_update)
+	if (freq == sg_policy->cached_raw_freq && !sg_policy->limits_changed &&
+	    !sg_policy->need_freq_update)
 		return sg_policy->next_freq;
 
+	sg_policy->limits_changed = false;
 	sg_policy->cached_raw_freq = freq;
 	return cpufreq_driver_resolve_freq(policy, freq);
 }
Signature · 1 line
2.49.0
#1 lore raw

Hi Sultan,

Quoted 37 lines · Sultan Alsawaf

From: Sultan Alsawaf <[email protected]>

When utilization is unchanged, a policy limits update is ignored unless CPUFREQ_NEED_UPDATE_LIMITS is set. This occurs because limits_changed depends on the old broken behavior of need_freq_update to trigger a call into cpufreq_driver_resolve_freq() to evaluate the changed policy limits.

After fixing need_freq_update, limit changes are ignored without CPUFREQ_NEED_UPDATE_LIMITS, at least until utilization changes enough to make map_util_freq() return something different.

Fix the ignored limit changes by preserving the value of limits_changed until get_next_freq() is called, so limits_changed can trigger a call to cpufreq_driver_resolve_freq().

Fixes 8e461a1cb43d6 ("cpufreq: schedutil: Fix superfluous updates caused by need_freq_update")
---
 kernel/sched/cpufreq_schedutil.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
kernel/sched/cpufreq_schedutil.c +0 -1
index 1a19d69b91ed3..f37b999854d52 100644
@@ -82,7 +82,6 @@ static bool sugov_should_update_freq(struct sugov_policy *sg_policy, u64 time)
                return false;
 
        if (unlikely(sg_policy->limits_changed)) {
-               sg_policy->limits_changed = false;
                sg_policy->need_freq_update = cpufreq_driver_test_flags(CPUFREQ_NEED_UPDATE_LIMITS);
                return true;
        }

kernel/sched/cpufreq_schedutil.c static unsigned int get_next_freq(struct sugov_policy *sg_policy, · line 174

Quoted 6 lines
kernel/sched/cpufreq_schedutil.c +2 -1
@@ -171,9 +170,11 @@ static unsigned int get_next_freq(struct sugov_policy *sg_policy,
        freq = get_capacity_ref_freq(policy);
        freq = map_util_freq(util, freq, max);
 
-       if (freq == sg_policy->cached_raw_freq && !sg_policy->need_freq_update)
+       if (freq == sg_policy->cached_raw_freq && !sg_policy->limits_changed &&
+           !sg_policy->need_freq_update)

As said in:https://lore.kernel.org/all/CAB8ipk8ARRdR8UPgLqJ3EcAzuE4KNEO=cmLNJLk6thTxdBSHWw@mail.gmail.com/

We also should add the limits_changed in the sugov_update_single_freq().

Thanks!

Quoted 10 lines
kernel/sched/cpufreq_schedutil.c +1 -1
                return sg_policy->next_freq;
 
+       sg_policy->limits_changed = false;
        sg_policy->cached_raw_freq = freq;
        return cpufreq_driver_resolve_freq(policy, freq);
 }
--

2.49.0

#2 lore raw
In reply to Sultan Alsawaf
Quoted 37 lines · Sultan Alsawaf

From: Sultan Alsawaf <[email protected]>

When utilization is unchanged, a policy limits update is ignored unless CPUFREQ_NEED_UPDATE_LIMITS is set. This occurs because limits_changed depends on the old broken behavior of need_freq_update to trigger a call into cpufreq_driver_resolve_freq() to evaluate the changed policy limits.

After fixing need_freq_update, limit changes are ignored without CPUFREQ_NEED_UPDATE_LIMITS, at least until utilization changes enough to make map_util_freq() return something different.

Fix the ignored limit changes by preserving the value of limits_changed until get_next_freq() is called, so limits_changed can trigger a call to cpufreq_driver_resolve_freq().

Fixes 8e461a1cb43d6 ("cpufreq: schedutil: Fix superfluous updates caused by need_freq_update")
---
 kernel/sched/cpufreq_schedutil.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
kernel/sched/cpufreq_schedutil.c +0 -1
index 1a19d69b91ed3..f37b999854d52 100644
@@ -82,7 +82,6 @@ static bool sugov_should_update_freq(struct sugov_policy *sg_policy, u64 time)
                return false;
 
        if (unlikely(sg_policy->limits_changed)) {
-               sg_policy->limits_changed = false;
                sg_policy->need_freq_update = cpufreq_driver_test_flags(CPUFREQ_NEED_UPDATE_LIMITS);
                return true;
        }

kernel/sched/cpufreq_schedutil.c static unsigned int get_next_freq(struct sugov_policy *sg_policy, · line 177

Quoted 9 lines
kernel/sched/cpufreq_schedutil.c +3 -1
@@ -171,9 +170,11 @@ static unsigned int get_next_freq(struct sugov_policy *sg_policy,
        freq = get_capacity_ref_freq(policy);
        freq = map_util_freq(util, freq, max);
 
-       if (freq == sg_policy->cached_raw_freq && !sg_policy->need_freq_update)
+       if (freq == sg_policy->cached_raw_freq && !sg_policy->limits_changed &&
+           !sg_policy->need_freq_update)
                return sg_policy->next_freq;
 
+       sg_policy->limits_changed = false;

AFAICS, after this code modification, a limit change may be missed due to a possible race with sugov_limits() which cannot happen if sg_policy->limits_changed is only cleared when it is set before updating sg_policy->need_freq_update.

Quoted 6 lines
kernel/sched/cpufreq_schedutil.c +0 -1
        sg_policy->cached_raw_freq = freq;
        return cpufreq_driver_resolve_freq(policy, freq);
 }
--

2.49.0

#3 lore raw
Quoted 46 lines · Rafael J. Wysocki
Quoted 45 lines · Sultan Alsawaf

From: Sultan Alsawaf <[email protected]>

When utilization is unchanged, a policy limits update is ignored unless CPUFREQ_NEED_UPDATE_LIMITS is set. This occurs because limits_changed depends on the old broken behavior of need_freq_update to trigger a call into cpufreq_driver_resolve_freq() to evaluate the changed policy limits.

After fixing need_freq_update, limit changes are ignored without CPUFREQ_NEED_UPDATE_LIMITS, at least until utilization changes enough to make map_util_freq() return something different.

Fix the ignored limit changes by preserving the value of limits_changed until get_next_freq() is called, so limits_changed can trigger a call to cpufreq_driver_resolve_freq().

Fixes 8e461a1cb43d6 ("cpufreq: schedutil: Fix superfluous updates caused by need_freq_update")
---
 kernel/sched/cpufreq_schedutil.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
kernel/sched/cpufreq_schedutil.c +2 -2
index 1a19d69b91ed3..f37b999854d52 100644
@@ -82,7 +82,6 @@ static bool sugov_should_update_freq(struct sugov_policy *sg_policy, u64 time)
                return false;
 
        if (unlikely(sg_policy->limits_changed)) {
-               sg_policy->limits_changed = false;
                sg_policy->need_freq_update = cpufreq_driver_test_flags(CPUFREQ_NEED_UPDATE_LIMITS);
                return true;
        }
@@ -171,9 +170,11 @@ static unsigned int get_next_freq(struct sugov_policy *sg_policy,
        freq = get_capacity_ref_freq(policy);
        freq = map_util_freq(util, freq, max);
 
-       if (freq == sg_policy->cached_raw_freq && !sg_policy->need_freq_update)
+       if (freq == sg_policy->cached_raw_freq && !sg_policy->limits_changed &&
+           !sg_policy->need_freq_update)
                return sg_policy->next_freq;
 
Quoted 6 lines

kernel/sched/cpufreq_schedutil.c static unsigned int get_next_freq(struct sugov_policy *sg_policy, · line 177

Quoted 1 line
kernel/sched/cpufreq_schedutil.c +1 -0
+       sg_policy->limits_changed = false;

AFAICS, after this code modification, a limit change may be missed due to a possible race with sugov_limits() which cannot happen if sg_policy->limits_changed is only cleared when it is set before updating sg_policy->need_freq_update.

I don't think that's the case because sg_policy->limits_changed is cleared before the new policy limits are evaluated in cpufreq_driver_resolve_freq(). Granted, if we wanted to be really certain of this, we'd need release semantics.

Looking closer at cpufreq.c actually, isn't there already a race on the updated policy limits (policy->min and policy->max) since they can be updated again while schedutil reads them via cpufreq_driver_resolve_freq()?

Sultan

#4 lore raw
Quoted 55 lines · Sultan Alsawaf
Quoted 46 lines · Rafael J. Wysocki
Quoted 45 lines · Sultan Alsawaf

From: Sultan Alsawaf <[email protected]>

When utilization is unchanged, a policy limits update is ignored unless CPUFREQ_NEED_UPDATE_LIMITS is set. This occurs because limits_changed depends on the old broken behavior of need_freq_update to trigger a call into cpufreq_driver_resolve_freq() to evaluate the changed policy limits.

After fixing need_freq_update, limit changes are ignored without CPUFREQ_NEED_UPDATE_LIMITS, at least until utilization changes enough to make map_util_freq() return something different.

Fix the ignored limit changes by preserving the value of limits_changed until get_next_freq() is called, so limits_changed can trigger a call to cpufreq_driver_resolve_freq().

Fixes 8e461a1cb43d6 ("cpufreq: schedutil: Fix superfluous updates caused by need_freq_update")
---
 kernel/sched/cpufreq_schedutil.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
kernel/sched/cpufreq_schedutil.c +2 -2
index 1a19d69b91ed3..f37b999854d52 100644
@@ -82,7 +82,6 @@ static bool sugov_should_update_freq(struct sugov_policy *sg_policy, u64 time)
                return false;
 
        if (unlikely(sg_policy->limits_changed)) {
-               sg_policy->limits_changed = false;
                sg_policy->need_freq_update = cpufreq_driver_test_flags(CPUFREQ_NEED_UPDATE_LIMITS);
                return true;
        }
@@ -171,9 +170,11 @@ static unsigned int get_next_freq(struct sugov_policy *sg_policy,
        freq = get_capacity_ref_freq(policy);
        freq = map_util_freq(util, freq, max);
 
-       if (freq == sg_policy->cached_raw_freq && !sg_policy->need_freq_update)
+       if (freq == sg_policy->cached_raw_freq && !sg_policy->limits_changed &&
+           !sg_policy->need_freq_update)
                return sg_policy->next_freq;
 
Quoted 6 lines

kernel/sched/cpufreq_schedutil.c static unsigned int get_next_freq(struct sugov_policy *sg_policy, · line 177

Quoted 1 line
kernel/sched/cpufreq_schedutil.c +1 -0
+       sg_policy->limits_changed = false;

AFAICS, after this code modification, a limit change may be missed due to a possible race with sugov_limits() which cannot happen if sg_policy->limits_changed is only cleared when it is set before updating sg_policy->need_freq_update.

Quoted 2 lines

I don't think that's the case because sg_policy->limits_changed is cleared before the new policy limits are evaluated in cpufreq_driver_resolve_freq().

sugov_limits() may be triggered by a scaling_max_freq update, for instance, so it is asynchronous with respect to the usual governor flow. It updates sg_policy->limits_changed and assumes that next time the governor runs, it will call into the driver, for example via cpufreq_driver_fast_switch(), so the new limits take effect. This is not about cpufreq_driver_resolve_freq().

sugov_limits() runs after the driver's ->verify() callback has returned and it is conditional on that callback's return value, so the driver already knows the new limits when sugov_limits() runs, but it may still need to tell the hardware what the new limits are and that's why cpufreq_driver_fast_switch() may need to run.

Now, if sugov_should_update_freq() sees sg_policy->limits_changed set, it will set sg_policy->need_freq_update which (for drivers with CPUFREQ_NEED_UPDATE_LIMITS set) guarantees that the driver will be invoked and so sg_policy->limits_changed can be cleared.

If a new instance of sugov_limits() runs at this point, there are two possibilities. Either it completes before the sg_policy->limits_changed update in sugov_should_update_freq(), in which case the driver already knows the new limits as per the above and so the subsequent invocation of cpufreq_driver_fast_switch() will pick them up, or it sets sg_policy->limits_changed again and the governor will see it set next time it runs. In both cases the new limits will be picked up unless they are changed again in the meantime.

After the above change, sg_policy->limits_changed may be cleared even if it has not been set before and that's problematic. Namely, say it is unset when sugov_should_update_freq() runs, after being called by sugov_update_single_freq() via sugov_update_single_common(), and returns 'true' without setting sg_policy->need_freq_update. Next, sugov_update_single_common() returns 'true' and get_next_freq() is called. It sees that freq != sg_policy->cached_raw_freq, so it clears sg_policy->limits_changed. If sugov_limits() runs on a different CPU between the check and the sg_policy->limits_changed update in get_next_freq(), it may be missed and it is still not guaranteed that cpufreq_driver_fast_switch() will run because sg_policy->need_freq_update is unset and sugov_hold_freq() may return 'true'.

For this to work, sg_policy->limits_changed needs to be cleared only when it is set and sg_policy->need_freq_update needs to be updated when sg_policy->limits_changed is cleared.

It looks like you really want to set sg_policy->need_freq_update to 'true' in sugov_should_update_freq() when sg_policy->limits_changed is set, but that would render CPUFREQ_NEED_UPDATE_LIMITS unnecessary.

Quoted 1 line

Granted, if we wanted to be really certain of this, we'd need release semantics.

I don't think so, but feel free to prove me wrong.

Quoted 3 lines

Looking closer at cpufreq.c actually, isn't there already a race on the updated policy limits (policy->min and policy->max) since they can be updated again while schedutil reads them via cpufreq_driver_resolve_freq()?

That can happen, but it is a different thing.

#5 lore raw
Quoted 102 lines · Rafael J. Wysocki
Quoted 55 lines · Sultan Alsawaf
Quoted 46 lines · Rafael J. Wysocki
Quoted 45 lines · Sultan Alsawaf

From: Sultan Alsawaf <[email protected]>

When utilization is unchanged, a policy limits update is ignored unless CPUFREQ_NEED_UPDATE_LIMITS is set. This occurs because limits_changed depends on the old broken behavior of need_freq_update to trigger a call into cpufreq_driver_resolve_freq() to evaluate the changed policy limits.

After fixing need_freq_update, limit changes are ignored without CPUFREQ_NEED_UPDATE_LIMITS, at least until utilization changes enough to make map_util_freq() return something different.

Fix the ignored limit changes by preserving the value of limits_changed until get_next_freq() is called, so limits_changed can trigger a call to cpufreq_driver_resolve_freq().

Fixes 8e461a1cb43d6 ("cpufreq: schedutil: Fix superfluous updates caused by need_freq_update")
---
 kernel/sched/cpufreq_schedutil.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
kernel/sched/cpufreq_schedutil.c +2 -2
index 1a19d69b91ed3..f37b999854d52 100644
@@ -82,7 +82,6 @@ static bool sugov_should_update_freq(struct sugov_policy *sg_policy, u64 time)
                return false;
 
        if (unlikely(sg_policy->limits_changed)) {
-               sg_policy->limits_changed = false;
                sg_policy->need_freq_update = cpufreq_driver_test_flags(CPUFREQ_NEED_UPDATE_LIMITS);
                return true;
        }
@@ -171,9 +170,11 @@ static unsigned int get_next_freq(struct sugov_policy *sg_policy,
        freq = get_capacity_ref_freq(policy);
        freq = map_util_freq(util, freq, max);
 
-       if (freq == sg_policy->cached_raw_freq && !sg_policy->need_freq_update)
+       if (freq == sg_policy->cached_raw_freq && !sg_policy->limits_changed &&
+           !sg_policy->need_freq_update)
                return sg_policy->next_freq;
 
Quoted 6 lines

kernel/sched/cpufreq_schedutil.c static unsigned int get_next_freq(struct sugov_policy *sg_policy, · line 177

Quoted 1 line
kernel/sched/cpufreq_schedutil.c +1 -0
+       sg_policy->limits_changed = false;

AFAICS, after this code modification, a limit change may be missed due to a possible race with sugov_limits() which cannot happen if sg_policy->limits_changed is only cleared when it is set before updating sg_policy->need_freq_update.

Quoted 2 lines

I don't think that's the case because sg_policy->limits_changed is cleared before the new policy limits are evaluated in cpufreq_driver_resolve_freq().

sugov_limits() may be triggered by a scaling_max_freq update, for instance, so it is asynchronous with respect to the usual governor flow. It updates sg_policy->limits_changed and assumes that next time the governor runs, it will call into the driver, for example via cpufreq_driver_fast_switch(), so the new limits take effect. This is not about cpufreq_driver_resolve_freq().

sugov_limits() runs after the driver's ->verify() callback has returned and it is conditional on that callback's return value, so the driver already knows the new limits when sugov_limits() runs, but it may still need to tell the hardware what the new limits are and that's why cpufreq_driver_fast_switch() may need to run.

Now, if sugov_should_update_freq() sees sg_policy->limits_changed set, it will set sg_policy->need_freq_update which (for drivers with CPUFREQ_NEED_UPDATE_LIMITS set) guarantees that the driver will be invoked and so sg_policy->limits_changed can be cleared.

If a new instance of sugov_limits() runs at this point, there are two possibilities. Either it completes before the sg_policy->limits_changed update in sugov_should_update_freq(), in which case the driver already knows the new limits as per the above and so the subsequent invocation of cpufreq_driver_fast_switch() will pick them up, or it sets sg_policy->limits_changed again and the governor will see it set next time it runs. In both cases the new limits will be picked up unless they are changed again in the meantime.

After the above change, sg_policy->limits_changed may be cleared even if it has not been set before and that's problematic. Namely, say it is unset when sugov_should_update_freq() runs, after being called by sugov_update_single_freq() via sugov_update_single_common(), and returns 'true' without setting sg_policy->need_freq_update. Next, sugov_update_single_common() returns 'true' and get_next_freq() is called. It sees that freq != sg_policy->cached_raw_freq, so it clears sg_policy->limits_changed. If sugov_limits() runs on a different CPU between the check and the sg_policy->limits_changed update in get_next_freq(), it may be missed and it is still not guaranteed that cpufreq_driver_fast_switch() will run because sg_policy->need_freq_update is unset and sugov_hold_freq() may return 'true'.

Quoted 7 lines

For this to work, sg_policy->limits_changed needs to be cleared only when it is set and sg_policy->need_freq_update needs to be updated when sg_policy->limits_changed is cleared.

It looks like you really want to set sg_policy->need_freq_update to 'true' in sugov_should_update_freq() when sg_policy->limits_changed is set, but that would render CPUFREQ_NEED_UPDATE_LIMITS unnecessary.

As I've just said in the original patch thread, it looks like using sg_policy->policy->fast_switch_enabled instead of cpufreq_driver_test_flags(CPUFREQ_NEED_UPDATE_LIMITS) in sugov_should_update_freq() should do the trick and then (if this works), CPUFREQ_NEED_UPDATE_LIMITS can be dropped.

#6 lore raw
In reply to Rafael J. Wysocki
Quoted 59 lines · Rafael J. Wysocki
Quoted 55 lines · Sultan Alsawaf
Quoted 46 lines · Rafael J. Wysocki
Quoted 45 lines · Sultan Alsawaf

From: Sultan Alsawaf <[email protected]>

When utilization is unchanged, a policy limits update is ignored unless CPUFREQ_NEED_UPDATE_LIMITS is set. This occurs because limits_changed depends on the old broken behavior of need_freq_update to trigger a call into cpufreq_driver_resolve_freq() to evaluate the changed policy limits.

After fixing need_freq_update, limit changes are ignored without CPUFREQ_NEED_UPDATE_LIMITS, at least until utilization changes enough to make map_util_freq() return something different.

Fix the ignored limit changes by preserving the value of limits_changed until get_next_freq() is called, so limits_changed can trigger a call to cpufreq_driver_resolve_freq().

Fixes 8e461a1cb43d6 ("cpufreq: schedutil: Fix superfluous updates caused by need_freq_update")
---
 kernel/sched/cpufreq_schedutil.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
kernel/sched/cpufreq_schedutil.c +2 -2
index 1a19d69b91ed3..f37b999854d52 100644
@@ -82,7 +82,6 @@ static bool sugov_should_update_freq(struct sugov_policy *sg_policy, u64 time)
                return false;
 
        if (unlikely(sg_policy->limits_changed)) {
-               sg_policy->limits_changed = false;
                sg_policy->need_freq_update = cpufreq_driver_test_flags(CPUFREQ_NEED_UPDATE_LIMITS);
                return true;
        }
@@ -171,9 +170,11 @@ static unsigned int get_next_freq(struct sugov_policy *sg_policy,
        freq = get_capacity_ref_freq(policy);
        freq = map_util_freq(util, freq, max);
 
-       if (freq == sg_policy->cached_raw_freq && !sg_policy->need_freq_update)
+       if (freq == sg_policy->cached_raw_freq && !sg_policy->limits_changed &&
+           !sg_policy->need_freq_update)
                return sg_policy->next_freq;
 
Quoted 6 lines

kernel/sched/cpufreq_schedutil.c static unsigned int get_next_freq(struct sugov_policy *sg_policy, · line 177

Quoted 1 line
kernel/sched/cpufreq_schedutil.c +1 -0
+       sg_policy->limits_changed = false;

AFAICS, after this code modification, a limit change may be missed due to a possible race with sugov_limits() which cannot happen if sg_policy->limits_changed is only cleared when it is set before updating sg_policy->need_freq_update.

Quoted 2 lines

I don't think that's the case because sg_policy->limits_changed is cleared before the new policy limits are evaluated in cpufreq_driver_resolve_freq().

Quoted 6 lines

sugov_limits() may be triggered by a scaling_max_freq update, for instance, so it is asynchronous with respect to the usual governor flow. It updates sg_policy->limits_changed and assumes that next time the governor runs, it will call into the driver, for example via cpufreq_driver_fast_switch(), so the new limits take effect. This is not about cpufreq_driver_resolve_freq().

OK, though I think there's still a race in cpufreq_driver_resolve_freq().

Quoted 5 lines

sugov_limits() runs after the driver's ->verify() callback has returned and it is conditional on that callback's return value, so the driver already knows the new limits when sugov_limits() runs, but it may still need to tell the hardware what the new limits are and that's why cpufreq_driver_fast_switch() may need to run.

Which is why CPUFREQ_NEED_UPDATE_LIMITS exists, right.

Quoted 29 lines

Now, if sugov_should_update_freq() sees sg_policy->limits_changed set, it will set sg_policy->need_freq_update which (for drivers with CPUFREQ_NEED_UPDATE_LIMITS set) guarantees that the driver will be invoked and so sg_policy->limits_changed can be cleared.

If a new instance of sugov_limits() runs at this point, there are two possibilities. Either it completes before the sg_policy->limits_changed update in sugov_should_update_freq(), in which case the driver already knows the new limits as per the above and so the subsequent invocation of cpufreq_driver_fast_switch() will pick them up, or it sets sg_policy->limits_changed again and the governor will see it set next time it runs. In both cases the new limits will be picked up unless they are changed again in the meantime.

After the above change, sg_policy->limits_changed may be cleared even if it has not been set before and that's problematic. Namely, say it is unset when sugov_should_update_freq() runs, after being called by sugov_update_single_freq() via sugov_update_single_common(), and returns 'true' without setting sg_policy->need_freq_update. Next, sugov_update_single_common() returns 'true' and get_next_freq() is called. It sees that freq != sg_policy->cached_raw_freq, so it clears sg_policy->limits_changed. If sugov_limits() runs on a different CPU between the check and the sg_policy->limits_changed update in get_next_freq(), it may be missed and it is still not guaranteed that cpufreq_driver_fast_switch() will run because sg_policy->need_freq_update is unset and sugov_hold_freq() may return 'true'.

Quoted 3 lines

For this to work, sg_policy->limits_changed needs to be cleared only when it is set and sg_policy->need_freq_update needs to be updated when sg_policy->limits_changed is cleared.

Ah I see, thank you for the detailed explanation. So if I am understanding it
correctly: the problem with my patch is that CPUFREQ_NEED_UPDATE_LIMITS might not be honored after a limits change, because CPUFREQ_NEED_UPDATE_LIMITS is only honored when sg_policy->limits_changed is set. So there is the following race:

CPU-A CPU-B

  sugov_should_update_freq()                    // sg_policy->limits_changed == false, sg_policy->need_freq_update == false
                                sugov_limits()  // sg_policy->limits_changed == true,  sg_policy->need_freq_update == false
  get_next_freq()                               // sg_policy->limits_changed == false, sg_policy->need_freq_update == false

// cpufreq driver won't be invoked for the limits change if:
// next_f == sg_policy->next_freq || (sugov_hold_freq() == true && next_f < sg_policy->next_freq)

Does that look right?

Quoted 7 lines

It looks like you really want to set sg_policy->need_freq_update to 'true' in sugov_should_update_freq() when sg_policy->limits_changed is set, but that would render CPUFREQ_NEED_UPDATE_LIMITS unnecessary.

Quoted 1 line

Granted, if we wanted to be really certain of this, we'd need release semantics.

I don't think so, but feel free to prove me wrong.

Well, it appears that there really is synchronization missing between cpufreq_set_policy() and schedutil, since cpufreq_set_policy() changes the live policy->min and policy->max, and schedutil may observe either the old values in there or garbage values due to load/store tearing.

Sultan

#7 lore raw
Quoted 117 lines · Sultan Alsawaf
Quoted 59 lines · Rafael J. Wysocki
Quoted 55 lines · Sultan Alsawaf
Quoted 46 lines · Rafael J. Wysocki
Quoted 45 lines · Sultan Alsawaf

From: Sultan Alsawaf <[email protected]>

When utilization is unchanged, a policy limits update is ignored unless CPUFREQ_NEED_UPDATE_LIMITS is set. This occurs because limits_changed depends on the old broken behavior of need_freq_update to trigger a call into cpufreq_driver_resolve_freq() to evaluate the changed policy limits.

After fixing need_freq_update, limit changes are ignored without CPUFREQ_NEED_UPDATE_LIMITS, at least until utilization changes enough to make map_util_freq() return something different.

Fix the ignored limit changes by preserving the value of limits_changed until get_next_freq() is called, so limits_changed can trigger a call to cpufreq_driver_resolve_freq().

Fixes 8e461a1cb43d6 ("cpufreq: schedutil: Fix superfluous updates caused by need_freq_update")
---
 kernel/sched/cpufreq_schedutil.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
kernel/sched/cpufreq_schedutil.c +2 -2
index 1a19d69b91ed3..f37b999854d52 100644
@@ -82,7 +82,6 @@ static bool sugov_should_update_freq(struct sugov_policy *sg_policy, u64 time)
                return false;
 
        if (unlikely(sg_policy->limits_changed)) {
-               sg_policy->limits_changed = false;
                sg_policy->need_freq_update = cpufreq_driver_test_flags(CPUFREQ_NEED_UPDATE_LIMITS);
                return true;
        }
@@ -171,9 +170,11 @@ static unsigned int get_next_freq(struct sugov_policy *sg_policy,
        freq = get_capacity_ref_freq(policy);
        freq = map_util_freq(util, freq, max);
 
-       if (freq == sg_policy->cached_raw_freq && !sg_policy->need_freq_update)
+       if (freq == sg_policy->cached_raw_freq && !sg_policy->limits_changed &&
+           !sg_policy->need_freq_update)
                return sg_policy->next_freq;
 
Quoted 6 lines

kernel/sched/cpufreq_schedutil.c static unsigned int get_next_freq(struct sugov_policy *sg_policy, · line 177

Quoted 1 line
kernel/sched/cpufreq_schedutil.c +1 -0
+       sg_policy->limits_changed = false;

AFAICS, after this code modification, a limit change may be missed due to a possible race with sugov_limits() which cannot happen if sg_policy->limits_changed is only cleared when it is set before updating sg_policy->need_freq_update.

Quoted 2 lines

I don't think that's the case because sg_policy->limits_changed is cleared before the new policy limits are evaluated in cpufreq_driver_resolve_freq().

Quoted 6 lines

sugov_limits() may be triggered by a scaling_max_freq update, for instance, so it is asynchronous with respect to the usual governor flow. It updates sg_policy->limits_changed and assumes that next time the governor runs, it will call into the driver, for example via cpufreq_driver_fast_switch(), so the new limits take effect. This is not about cpufreq_driver_resolve_freq().

OK, though I think there's still a race in cpufreq_driver_resolve_freq().

Quoted 5 lines

sugov_limits() runs after the driver's ->verify() callback has returned and it is conditional on that callback's return value, so the driver already knows the new limits when sugov_limits() runs, but it may still need to tell the hardware what the new limits are and that's why cpufreq_driver_fast_switch() may need to run.

Which is why CPUFREQ_NEED_UPDATE_LIMITS exists, right.

Quoted 29 lines

Now, if sugov_should_update_freq() sees sg_policy->limits_changed set, it will set sg_policy->need_freq_update which (for drivers with CPUFREQ_NEED_UPDATE_LIMITS set) guarantees that the driver will be invoked and so sg_policy->limits_changed can be cleared.

If a new instance of sugov_limits() runs at this point, there are two possibilities. Either it completes before the sg_policy->limits_changed update in sugov_should_update_freq(), in which case the driver already knows the new limits as per the above and so the subsequent invocation of cpufreq_driver_fast_switch() will pick them up, or it sets sg_policy->limits_changed again and the governor will see it set next time it runs. In both cases the new limits will be picked up unless they are changed again in the meantime.

After the above change, sg_policy->limits_changed may be cleared even if it has not been set before and that's problematic. Namely, say it is unset when sugov_should_update_freq() runs, after being called by sugov_update_single_freq() via sugov_update_single_common(), and returns 'true' without setting sg_policy->need_freq_update. Next, sugov_update_single_common() returns 'true' and get_next_freq() is called. It sees that freq != sg_policy->cached_raw_freq, so it clears sg_policy->limits_changed. If sugov_limits() runs on a different CPU between the check and the sg_policy->limits_changed update in get_next_freq(), it may be missed and it is still not guaranteed that cpufreq_driver_fast_switch() will run because sg_policy->need_freq_update is unset and sugov_hold_freq() may return 'true'.

Quoted 3 lines

For this to work, sg_policy->limits_changed needs to be cleared only when it is set and sg_policy->need_freq_update needs to be updated when sg_policy->limits_changed is cleared.

Ah I see, thank you for the detailed explanation. So if I am understanding it
correctly: the problem with my patch is that CPUFREQ_NEED_UPDATE_LIMITS might not be honored after a limits change, because CPUFREQ_NEED_UPDATE_LIMITS is only honored when sg_policy->limits_changed is set. So there is the following race:

CPU-A CPU-B

Quoted 8 lines
  sugov_should_update_freq()                    // sg_policy->limits_changed == false, sg_policy->need_freq_update == false
                                sugov_limits()  // sg_policy->limits_changed == true,  sg_policy->need_freq_update == false
  get_next_freq()                               // sg_policy->limits_changed == false, sg_policy->need_freq_update == false

// cpufreq driver won't be invoked for the limits change if:
// next_f == sg_policy->next_freq || (sugov_hold_freq() == true && next_f < sg_policy->next_freq)

Does that look right?

Yes, it does.

Quoted 4 lines
Quoted 4 lines

It looks like you really want to set sg_policy->need_freq_update to 'true' in sugov_should_update_freq() when sg_policy->limits_changed is set, but that would render CPUFREQ_NEED_UPDATE_LIMITS unnecessary.

Quoted 8 lines
Quoted 3 lines
Quoted 1 line

Granted, if we wanted to be really certain of this, we'd need release semantics.

I don't think so, but feel free to prove me wrong.

Well, it appears that there really is synchronization missing between cpufreq_set_policy() and schedutil, since cpufreq_set_policy() changes the live policy->min and policy->max, and schedutil may observe either the old values in there or garbage values due to load/store tearing.

schedutil itself doesn't really read policy->min and policy->max. cpufreq_driver_resolve_freq() does this and drivers do, but since policy->min and policy->max are integers, no garbage values will be observed AFAICS.

Stale values can be observed, but in that case the new values will be seen next time and limits_changed enforces picking them up.

Of course, if the limits change sufficiently often, schedutil will fall behind, but then there is just too much noise anyway.

WRITE_ONCE() and READ_ONCE() could be used there, though, because the compiler can do some damage in principle.

#8 lore raw
Quoted 143 lines · Rafael J. Wysocki
Quoted 117 lines · Sultan Alsawaf
Quoted 59 lines · Rafael J. Wysocki
Quoted 55 lines · Sultan Alsawaf
Quoted 46 lines · Rafael J. Wysocki
Quoted 45 lines · Sultan Alsawaf

From: Sultan Alsawaf <[email protected]>

When utilization is unchanged, a policy limits update is ignored unless CPUFREQ_NEED_UPDATE_LIMITS is set. This occurs because limits_changed depends on the old broken behavior of need_freq_update to trigger a call into cpufreq_driver_resolve_freq() to evaluate the changed policy limits.

After fixing need_freq_update, limit changes are ignored without CPUFREQ_NEED_UPDATE_LIMITS, at least until utilization changes enough to make map_util_freq() return something different.

Fix the ignored limit changes by preserving the value of limits_changed until get_next_freq() is called, so limits_changed can trigger a call to cpufreq_driver_resolve_freq().

Fixes 8e461a1cb43d6 ("cpufreq: schedutil: Fix superfluous updates caused by need_freq_update")
---
 kernel/sched/cpufreq_schedutil.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)
kernel/sched/cpufreq_schedutil.c +2 -2
index 1a19d69b91ed3..f37b999854d52 100644
@@ -82,7 +82,6 @@ static bool sugov_should_update_freq(struct sugov_policy *sg_policy, u64 time)
                return false;
 
        if (unlikely(sg_policy->limits_changed)) {
-               sg_policy->limits_changed = false;
                sg_policy->need_freq_update = cpufreq_driver_test_flags(CPUFREQ_NEED_UPDATE_LIMITS);
                return true;
        }
@@ -171,9 +170,11 @@ static unsigned int get_next_freq(struct sugov_policy *sg_policy,
        freq = get_capacity_ref_freq(policy);
        freq = map_util_freq(util, freq, max);
 
-       if (freq == sg_policy->cached_raw_freq && !sg_policy->need_freq_update)
+       if (freq == sg_policy->cached_raw_freq && !sg_policy->limits_changed &&
+           !sg_policy->need_freq_update)
                return sg_policy->next_freq;
 
Quoted 6 lines

kernel/sched/cpufreq_schedutil.c static unsigned int get_next_freq(struct sugov_policy *sg_policy, · line 177

Quoted 1 line
kernel/sched/cpufreq_schedutil.c +1 -0
+       sg_policy->limits_changed = false;

AFAICS, after this code modification, a limit change may be missed due to a possible race with sugov_limits() which cannot happen if sg_policy->limits_changed is only cleared when it is set before updating sg_policy->need_freq_update.

Quoted 2 lines

I don't think that's the case because sg_policy->limits_changed is cleared before the new policy limits are evaluated in cpufreq_driver_resolve_freq().

Quoted 6 lines

sugov_limits() may be triggered by a scaling_max_freq update, for instance, so it is asynchronous with respect to the usual governor flow. It updates sg_policy->limits_changed and assumes that next time the governor runs, it will call into the driver, for example via cpufreq_driver_fast_switch(), so the new limits take effect. This is not about cpufreq_driver_resolve_freq().

OK, though I think there's still a race in cpufreq_driver_resolve_freq().

Quoted 5 lines

sugov_limits() runs after the driver's ->verify() callback has returned and it is conditional on that callback's return value, so the driver already knows the new limits when sugov_limits() runs, but it may still need to tell the hardware what the new limits are and that's why cpufreq_driver_fast_switch() may need to run.

Which is why CPUFREQ_NEED_UPDATE_LIMITS exists, right.

Quoted 29 lines

Now, if sugov_should_update_freq() sees sg_policy->limits_changed set, it will set sg_policy->need_freq_update which (for drivers with CPUFREQ_NEED_UPDATE_LIMITS set) guarantees that the driver will be invoked and so sg_policy->limits_changed can be cleared.

If a new instance of sugov_limits() runs at this point, there are two possibilities. Either it completes before the sg_policy->limits_changed update in sugov_should_update_freq(), in which case the driver already knows the new limits as per the above and so the subsequent invocation of cpufreq_driver_fast_switch() will pick them up, or it sets sg_policy->limits_changed again and the governor will see it set next time it runs. In both cases the new limits will be picked up unless they are changed again in the meantime.

After the above change, sg_policy->limits_changed may be cleared even if it has not been set before and that's problematic. Namely, say it is unset when sugov_should_update_freq() runs, after being called by sugov_update_single_freq() via sugov_update_single_common(), and returns 'true' without setting sg_policy->need_freq_update. Next, sugov_update_single_common() returns 'true' and get_next_freq() is called. It sees that freq != sg_policy->cached_raw_freq, so it clears sg_policy->limits_changed. If sugov_limits() runs on a different CPU between the check and the sg_policy->limits_changed update in get_next_freq(), it may be missed and it is still not guaranteed that cpufreq_driver_fast_switch() will run because sg_policy->need_freq_update is unset and sugov_hold_freq() may return 'true'.

Quoted 3 lines

For this to work, sg_policy->limits_changed needs to be cleared only when it is set and sg_policy->need_freq_update needs to be updated when sg_policy->limits_changed is cleared.

Ah I see, thank you for the detailed explanation. So if I am understanding it
correctly: the problem with my patch is that CPUFREQ_NEED_UPDATE_LIMITS might not be honored after a limits change, because CPUFREQ_NEED_UPDATE_LIMITS is only honored when sg_policy->limits_changed is set. So there is the following race:

CPU-A CPU-B

Quoted 8 lines
  sugov_should_update_freq()                    // sg_policy->limits_changed == false, sg_policy->need_freq_update == false
                                sugov_limits()  // sg_policy->limits_changed == true,  sg_policy->need_freq_update == false
  get_next_freq()                               // sg_policy->limits_changed == false, sg_policy->need_freq_update == false

// cpufreq driver won't be invoked for the limits change if:
// next_f == sg_policy->next_freq || (sugov_hold_freq() == true && next_f < sg_policy->next_freq)

Does that look right?

Yes, it does.

Quoted 4 lines
Quoted 4 lines

It looks like you really want to set sg_policy->need_freq_update to 'true' in sugov_should_update_freq() when sg_policy->limits_changed is set, but that would render CPUFREQ_NEED_UPDATE_LIMITS unnecessary.

Quoted 8 lines
Quoted 3 lines
Quoted 1 line

Granted, if we wanted to be really certain of this, we'd need release semantics.

I don't think so, but feel free to prove me wrong.

Well, it appears that there really is synchronization missing between cpufreq_set_policy() and schedutil, since cpufreq_set_policy() changes the live policy->min and policy->max, and schedutil may observe either the old values in there or garbage values due to load/store tearing.

Quoted 4 lines

schedutil itself doesn't really read policy->min and policy->max. cpufreq_driver_resolve_freq() does this and drivers do, but since policy->min and policy->max are integers, no garbage values will be observed AFAICS.

Well, I've just realized that this is not exactly the case because of the way in which __resolve_freq() is used in cpufreq_set_policy(). That needs to be fixed.

#10 lore raw
Quoted 11 lines · Xuewen Yan

...

Quoted 6 lines

AFAICS, after this code modification, a limit change may be missed due to a possible race with sugov_limits() which cannot happen if sg_policy->limits_changed is only cleared when it is set before updating sg_policy->need_freq_update.

could the following patch prevent the race?

https://lore.kernel.org/all/CAB8ipk_Ayqmh=Ch2aH2c+i-q+qdiQ317VBH1kOHYN=R9dt6LOw@mail.gmail.com/

The first hunk is essentially a partial revert of the problematic commit, but I'm not sure what you want to achieve with the second one.