Skip to content

Commit 1bace01

Browse files
module/cpufreq: Fix async use_governor()
use_governor() was trying to set concurrently both per-cpu and global tunables for each governor, which lead to a write conflict. Split the work into the per-governor global tunables and the per-cpu tunables, and do all that in concurrently. Each task is therefore responsible of a distinct set of files and all is well. Also remove @memoized on async functions. It will be reintroduced in a later commit when there is a safe alternative for async functions.
1 parent e1e4524 commit 1bace01

File tree

1 file changed

+70
-29
lines changed

1 file changed

+70
-29
lines changed

devlib/module/cpufreq.py

Lines changed: 70 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ def __init__(self, target):
5858
super(CpufreqModule, self).__init__(target)
5959
self._governor_tunables = {}
6060

61-
@memoized
6261
@asyn.asyncf
6362
async def list_governors(self, cpu):
6463
"""Returns a list of governors supported by the cpu."""
@@ -105,7 +104,7 @@ async def set_governor(self, cpu, governor, **kwargs):
105104
raise TargetStableError('Governor {} not supported for cpu {}'.format(governor, cpu))
106105
sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_governor'.format(cpu)
107106
await self.target.write_value.asyn(sysfile, governor)
108-
return await self.set_governor_tunables.asyn(cpu, governor, **kwargs)
107+
await self.set_governor_tunables.asyn(cpu, governor, **kwargs)
109108

110109
@asyn.asynccontextmanager
111110
async def use_governor(self, governor, cpus=None, **kwargs):
@@ -151,35 +150,74 @@ async def get_cpu_info(cpu):
151150
try:
152151
yield
153152
finally:
154-
async def set_gov(cpu):
153+
async def set_per_cpu_tunables(cpu):
155154
domain, prev_gov, tunables, freq = cpus_infos[cpu]
156-
await self.set_governor.asyn(cpu, prev_gov, **tunables)
155+
# Per-cpu tunables are safe to set concurrently
156+
await self.set_governor_tunables.asyn(cpu, prev_gov, per_cpu=True, **tunables)
157157
# Special case for userspace, frequency is not seen as a tunable
158158
if prev_gov == "userspace":
159159
await self.set_frequency.asyn(cpu, freq)
160160

161+
per_cpu_tunables = self.target.async_manager.concurrently(
162+
set_per_cpu_tunables(cpu)
163+
for cpu in domains
164+
)
165+
166+
# Non-per-cpu tunables have to be set one after the other, for each
167+
# governor that we had to deal with.
168+
global_tunables = {
169+
prev_gov: (cpu, tunables)
170+
for cpu, (domain, prev_gov, tunables, freq) in cpus_infos.items()
171+
}
172+
173+
global_tunables = self.target.async_manager.concurrently(
174+
self.set_governor_tunables.asyn(cpu, gov, per_cpu=False, **tunables)
175+
for gov, (cpu, tunables) in global_tunables.items()
176+
)
177+
178+
# Set the governor first
161179
await self.target.async_manager.concurrently(
162-
set_gov(cpu)
180+
self.set_governor.asyn(cpu, cpufs_infos[cpu][1])
163181
for cpu in domains
164182
)
183+
# And then set all the tunables concurrently. Each task has a
184+
# specific and non-overlapping set of file to write.
185+
await self.target.async_manager.concurrently(
186+
(per_cpu_tunables, global_tunables)
187+
)
165188

166189
@asyn.asyncf
167-
async def list_governor_tunables(self, cpu):
168-
"""Returns a list of tunables available for the governor on the specified CPU."""
190+
async def _list_governor_tunables(self, cpu):
169191
if isinstance(cpu, int):
170192
cpu = 'cpu{}'.format(cpu)
171193
governor = await self.get_governor.asyn(cpu)
172-
if governor not in self._governor_tunables:
173-
try:
174-
tunables_path = '/sys/devices/system/cpu/{}/cpufreq/{}'.format(cpu, governor)
175-
self._governor_tunables[governor] = await self.target.list_directory.asyn(tunables_path)
176-
except TargetStableError: # probably an older kernel
194+
195+
try:
196+
return self._governor_tunables[governor]
197+
except KeyError:
198+
for per_cpu, path in (
199+
(True, '/sys/devices/system/cpu/{}/cpufreq/{}'.format(cpu, governor)),
200+
# On old kernels
201+
(False, '/sys/devices/system/cpu/cpufreq/{}'.format(governor)),
202+
):
177203
try:
178-
tunables_path = '/sys/devices/system/cpu/cpufreq/{}'.format(governor)
179-
self._governor_tunables[governor] = await self.target.list_directory.asyn(tunables_path)
180-
except TargetStableError: # governor does not support tunables
181-
self._governor_tunables[governor] = []
182-
return self._governor_tunables[governor]
204+
tunables = await self.target.list_directory.asyn(tunables_path)
205+
except TargetStableError:
206+
continue
207+
else:
208+
break
209+
else:
210+
per_cpu = False
211+
tunables = []
212+
213+
self._governor_tunables[governor] = (per_cpu, tunables)
214+
return (per_cpu, tunables)
215+
216+
@asyn.asyncf
217+
async def list_governor_tunables(self, cpu):
218+
"""Returns a list of tunables available for the governor on the specified CPU."""
219+
_, tunables = await self._list_governor_tunables.asyn(cpu)
220+
return tunables
183221

184222
@asyn.asyncf
185223
async def get_governor_tunables(self, cpu):
@@ -211,7 +249,7 @@ async def get_tunable(tunable):
211249
return tunables
212250

213251
@asyn.asyncf
214-
async def set_governor_tunables(self, cpu, governor=None, **kwargs):
252+
async def set_governor_tunables(self, cpu, governor=None, per_cpu=None, **kwargs):
215253
"""
216254
Set tunables for the specified governor. Tunables should be specified as
217255
keyword arguments. Which tunables and values are valid depends on the
@@ -220,6 +258,9 @@ async def set_governor_tunables(self, cpu, governor=None, **kwargs):
220258
:param cpu: The cpu for which the governor will be set. ``int`` or
221259
full cpu name as it appears in sysfs, e.g. ``cpu0``.
222260
:param governor: The name of the governor. Must be all lower case.
261+
:param per_cpu: If ``None``, both per-cpu and global governor tunables
262+
will be set. If ``True``, only per-CPU tunables will be set and if
263+
``False``, only global tunables will be set.
223264
224265
The rest should be keyword parameters mapping tunable name onto the value to
225266
be set for it.
@@ -229,29 +270,29 @@ async def set_governor_tunables(self, cpu, governor=None, **kwargs):
229270
tunable.
230271
231272
"""
273+
if not kwargs:
274+
return
232275
if isinstance(cpu, int):
233276
cpu = 'cpu{}'.format(cpu)
234277
if governor is None:
235278
governor = await self.get_governor.asyn(cpu)
236-
valid_tunables = await self.list_governor_tunables.asyn(cpu)
279+
gov_per_cpu, valid_tunables = await self._list_governor_tunables.asyn(cpu)
237280
for tunable, value in kwargs.items():
238281
if tunable in valid_tunables:
239-
path = '/sys/devices/system/cpu/{}/cpufreq/{}/{}'.format(cpu, governor, tunable)
240-
try:
241-
await self.target.write_value.asyn(path, value)
242-
except TargetStableError:
243-
if await self.target.file_exists.asyn(path):
244-
# File exists but we did something wrong
245-
raise
246-
# Expected file doesn't exist, try older sysfs layout.
282+
if per_cpu is not None and gov_per_cpu != per_cpu:
283+
pass
284+
285+
if gov_per_cpu:
286+
path = '/sys/devices/system/cpu/{}/cpufreq/{}/{}'.format(cpu, governor, tunable)
287+
else:
247288
path = '/sys/devices/system/cpu/cpufreq/{}/{}'.format(governor, tunable)
248-
await self.target.write_value.asyn(path, value)
289+
290+
await self.target.write_value.asyn(path, value)
249291
else:
250292
message = 'Unexpected tunable {} for governor {} on {}.\n'.format(tunable, governor, cpu)
251293
message += 'Available tunables are: {}'.format(valid_tunables)
252294
raise TargetStableError(message)
253295

254-
@memoized
255296
@asyn.asyncf
256297
async def list_frequencies(self, cpu):
257298
"""Returns a sorted list of frequencies supported by the cpu or an empty list

0 commit comments

Comments
 (0)