1# Copyright 2014-2015 ARM Limited 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14# 15import json 16from devlib.module import Module 17from devlib.exception import TargetError 18from devlib.utils.misc import memoized 19 20 21# a dict of governor name and a list of it tunables that can't be read 22WRITE_ONLY_TUNABLES = { 23 'interactive': ['boostpulse'] 24} 25 26 27class CpufreqModule(Module): 28 29 name = 'cpufreq' 30 31 @staticmethod 32 def probe(target): 33 34 # x86 with Intel P-State driver 35 if target.abi == 'x86_64': 36 path = '/sys/devices/system/cpu/intel_pstate' 37 if target.file_exists(path): 38 return True 39 40 # Generic CPUFreq support (single policy) 41 path = '/sys/devices/system/cpu/cpufreq' 42 if target.file_exists(path): 43 return True 44 45 # Generic CPUFreq support (per CPU policy) 46 path = '/sys/devices/system/cpu/cpu0/cpufreq' 47 return target.file_exists(path) 48 49 def __init__(self, target): 50 super(CpufreqModule, self).__init__(target) 51 self._governor_tunables = {} 52 53 @memoized 54 def list_governors(self, cpu): 55 """Returns a list of governors supported by the cpu.""" 56 if isinstance(cpu, int): 57 cpu = 'cpu{}'.format(cpu) 58 sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_available_governors'.format(cpu) 59 output = self.target.read_value(sysfile) 60 return output.strip().split() 61 62 def get_governor(self, cpu): 63 """Returns the governor currently set for the specified CPU.""" 64 if isinstance(cpu, int): 65 cpu = 'cpu{}'.format(cpu) 66 sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_governor'.format(cpu) 67 return self.target.read_value(sysfile) 68 69 def set_governor(self, cpu, governor, **kwargs): 70 """ 71 Set the governor for the specified CPU. 72 See https://www.kernel.org/doc/Documentation/cpu-freq/governors.txt 73 74 :param cpu: The CPU for which the governor is to be set. This must be 75 the full name as it appears in sysfs, e.g. "cpu0". 76 :param governor: The name of the governor to be used. This must be 77 supported by the specific device. 78 79 Additional keyword arguments can be used to specify governor tunables for 80 governors that support them. 81 82 :note: On big.LITTLE all cores in a cluster must be using the same governor. 83 Setting the governor on any core in a cluster will also set it on all 84 other cores in that cluster. 85 86 :raises: TargetError if governor is not supported by the CPU, or if, 87 for some reason, the governor could not be set. 88 89 """ 90 if isinstance(cpu, int): 91 cpu = 'cpu{}'.format(cpu) 92 supported = self.list_governors(cpu) 93 if governor not in supported: 94 raise TargetError('Governor {} not supported for cpu {}'.format(governor, cpu)) 95 sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_governor'.format(cpu) 96 self.target.write_value(sysfile, governor) 97 self.set_governor_tunables(cpu, governor, **kwargs) 98 99 def list_governor_tunables(self, cpu): 100 """Returns a list of tunables available for the governor on the specified CPU.""" 101 if isinstance(cpu, int): 102 cpu = 'cpu{}'.format(cpu) 103 governor = self.get_governor(cpu) 104 if governor not in self._governor_tunables: 105 try: 106 tunables_path = '/sys/devices/system/cpu/{}/cpufreq/{}'.format(cpu, governor) 107 self._governor_tunables[governor] = self.target.list_directory(tunables_path) 108 except TargetError: # probably an older kernel 109 try: 110 tunables_path = '/sys/devices/system/cpu/cpufreq/{}'.format(governor) 111 self._governor_tunables[governor] = self.target.list_directory(tunables_path) 112 except TargetError: # governor does not support tunables 113 self._governor_tunables[governor] = [] 114 return self._governor_tunables[governor] 115 116 def get_governor_tunables(self, cpu): 117 if isinstance(cpu, int): 118 cpu = 'cpu{}'.format(cpu) 119 governor = self.get_governor(cpu) 120 tunables = {} 121 for tunable in self.list_governor_tunables(cpu): 122 if tunable not in WRITE_ONLY_TUNABLES.get(governor, []): 123 try: 124 path = '/sys/devices/system/cpu/{}/cpufreq/{}/{}'.format(cpu, governor, tunable) 125 tunables[tunable] = self.target.read_value(path) 126 except TargetError: # May be an older kernel 127 path = '/sys/devices/system/cpu/cpufreq/{}/{}'.format(governor, tunable) 128 tunables[tunable] = self.target.read_value(path) 129 return tunables 130 131 def set_governor_tunables(self, cpu, governor=None, **kwargs): 132 """ 133 Set tunables for the specified governor. Tunables should be specified as 134 keyword arguments. Which tunables and values are valid depends on the 135 governor. 136 137 :param cpu: The cpu for which the governor will be set. ``int`` or 138 full cpu name as it appears in sysfs, e.g. ``cpu0``. 139 :param governor: The name of the governor. Must be all lower case. 140 141 The rest should be keyword parameters mapping tunable name onto the value to 142 be set for it. 143 144 :raises: TargetError if governor specified is not a valid governor name, or if 145 a tunable specified is not valid for the governor, or if could not set 146 tunable. 147 148 """ 149 if isinstance(cpu, int): 150 cpu = 'cpu{}'.format(cpu) 151 if governor is None: 152 governor = self.get_governor(cpu) 153 valid_tunables = self.list_governor_tunables(cpu) 154 for tunable, value in kwargs.iteritems(): 155 if tunable in valid_tunables: 156 path = '/sys/devices/system/cpu/{}/cpufreq/{}/{}'.format(cpu, governor, tunable) 157 try: 158 self.target.write_value(path, value) 159 except TargetError: 160 if self.target.file_exists(path): 161 # File exists but we did something wrong 162 raise 163 # Expected file doesn't exist, try older sysfs layout. 164 path = '/sys/devices/system/cpu/cpufreq/{}/{}'.format(governor, tunable) 165 self.target.write_value(path, value) 166 else: 167 message = 'Unexpected tunable {} for governor {} on {}.\n'.format(tunable, governor, cpu) 168 message += 'Available tunables are: {}'.format(valid_tunables) 169 raise TargetError(message) 170 171 @memoized 172 def list_frequencies(self, cpu): 173 """Returns a list of frequencies supported by the cpu or an empty list 174 if not could be found.""" 175 if isinstance(cpu, int): 176 cpu = 'cpu{}'.format(cpu) 177 try: 178 cmd = 'cat /sys/devices/system/cpu/{}/cpufreq/scaling_available_frequencies'.format(cpu) 179 output = self.target.execute(cmd) 180 available_frequencies = map(int, output.strip().split()) # pylint: disable=E1103 181 except TargetError: 182 # On some devices scaling_frequencies is not generated. 183 # http://adrynalyne-teachtofish.blogspot.co.uk/2011/11/how-to-enable-scalingavailablefrequenci.html 184 # Fall back to parsing stats/time_in_state 185 cmd = 'cat /sys/devices/system/cpu/{}/cpufreq/stats/time_in_state'.format(cpu) 186 out_iter = iter(self.target.execute(cmd).strip().split()) 187 available_frequencies = map(int, reversed([f for f, _ in zip(out_iter, out_iter)])) 188 return available_frequencies 189 190 def get_min_frequency(self, cpu): 191 """ 192 Returns the min frequency currently set for the specified CPU. 193 194 Warning, this method does not check if the cpu is online or not. It will 195 try to read the minimum frequency and the following exception will be 196 raised :: 197 198 :raises: TargetError if for some reason the frequency could not be read. 199 200 """ 201 if isinstance(cpu, int): 202 cpu = 'cpu{}'.format(cpu) 203 sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_min_freq'.format(cpu) 204 return self.target.read_int(sysfile) 205 206 def set_min_frequency(self, cpu, frequency, exact=True): 207 """ 208 Set's the minimum value for CPU frequency. Actual frequency will 209 depend on the Governor used and may vary during execution. The value should be 210 either an int or a string representing an integer. The Value must also be 211 supported by the device. The available frequencies can be obtained by calling 212 get_frequencies() or examining 213 214 /sys/devices/system/cpu/cpuX/cpufreq/scaling_frequencies 215 216 on the device. 217 218 :raises: TargetError if the frequency is not supported by the CPU, or if, for 219 some reason, frequency could not be set. 220 :raises: ValueError if ``frequency`` is not an integer. 221 222 """ 223 if isinstance(cpu, int): 224 cpu = 'cpu{}'.format(cpu) 225 available_frequencies = self.list_frequencies(cpu) 226 try: 227 value = int(frequency) 228 if exact and available_frequencies and value not in available_frequencies: 229 raise TargetError('Can\'t set {} frequency to {}\nmust be in {}'.format(cpu, 230 value, 231 available_frequencies)) 232 sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_min_freq'.format(cpu) 233 self.target.write_value(sysfile, value) 234 except ValueError: 235 raise ValueError('Frequency must be an integer; got: "{}"'.format(frequency)) 236 237 def get_frequency(self, cpu): 238 """ 239 Returns the current frequency currently set for the specified CPU. 240 241 Warning, this method does not check if the cpu is online or not. It will 242 try to read the current frequency and the following exception will be 243 raised :: 244 245 :raises: TargetError if for some reason the frequency could not be read. 246 247 """ 248 if isinstance(cpu, int): 249 cpu = 'cpu{}'.format(cpu) 250 sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_cur_freq'.format(cpu) 251 return self.target.read_int(sysfile) 252 253 def set_frequency(self, cpu, frequency, exact=True): 254 """ 255 Set's the minimum value for CPU frequency. Actual frequency will 256 depend on the Governor used and may vary during execution. The value should be 257 either an int or a string representing an integer. 258 259 If ``exact`` flag is set (the default), the Value must also be supported by 260 the device. The available frequencies can be obtained by calling 261 get_frequencies() or examining 262 263 /sys/devices/system/cpu/cpuX/cpufreq/scaling_frequencies 264 265 on the device (if it exists). 266 267 :raises: TargetError if the frequency is not supported by the CPU, or if, for 268 some reason, frequency could not be set. 269 :raises: ValueError if ``frequency`` is not an integer. 270 271 """ 272 if isinstance(cpu, int): 273 cpu = 'cpu{}'.format(cpu) 274 try: 275 value = int(frequency) 276 if exact: 277 available_frequencies = self.list_frequencies(cpu) 278 if available_frequencies and value not in available_frequencies: 279 raise TargetError('Can\'t set {} frequency to {}\nmust be in {}'.format(cpu, 280 value, 281 available_frequencies)) 282 if self.get_governor(cpu) != 'userspace': 283 raise TargetError('Can\'t set {} frequency; governor must be "userspace"'.format(cpu)) 284 sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_setspeed'.format(cpu) 285 self.target.write_value(sysfile, value, verify=False) 286 except ValueError: 287 raise ValueError('Frequency must be an integer; got: "{}"'.format(frequency)) 288 289 def get_max_frequency(self, cpu): 290 """ 291 Returns the max frequency currently set for the specified CPU. 292 293 Warning, this method does not check if the cpu is online or not. It will 294 try to read the maximum frequency and the following exception will be 295 raised :: 296 297 :raises: TargetError if for some reason the frequency could not be read. 298 """ 299 if isinstance(cpu, int): 300 cpu = 'cpu{}'.format(cpu) 301 sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_max_freq'.format(cpu) 302 return self.target.read_int(sysfile) 303 304 def set_max_frequency(self, cpu, frequency, exact=True): 305 """ 306 Set's the minimum value for CPU frequency. Actual frequency will 307 depend on the Governor used and may vary during execution. The value should be 308 either an int or a string representing an integer. The Value must also be 309 supported by the device. The available frequencies can be obtained by calling 310 get_frequencies() or examining 311 312 /sys/devices/system/cpu/cpuX/cpufreq/scaling_frequencies 313 314 on the device. 315 316 :raises: TargetError if the frequency is not supported by the CPU, or if, for 317 some reason, frequency could not be set. 318 :raises: ValueError if ``frequency`` is not an integer. 319 320 """ 321 if isinstance(cpu, int): 322 cpu = 'cpu{}'.format(cpu) 323 available_frequencies = self.list_frequencies(cpu) 324 try: 325 value = int(frequency) 326 if exact and available_frequencies and value not in available_frequencies: 327 raise TargetError('Can\'t set {} frequency to {}\nmust be in {}'.format(cpu, 328 value, 329 available_frequencies)) 330 sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_max_freq'.format(cpu) 331 self.target.write_value(sysfile, value) 332 except ValueError: 333 raise ValueError('Frequency must be an integer; got: "{}"'.format(frequency)) 334 335 def set_governor_for_cpus(self, cpus, governor, **kwargs): 336 """ 337 Set the governor for the specified list of CPUs. 338 See https://www.kernel.org/doc/Documentation/cpu-freq/governors.txt 339 340 :param cpus: The list of CPU for which the governor is to be set. 341 """ 342 for cpu in cpus: 343 self.set_governor(cpu, governor, **kwargs) 344 345 def set_frequency_for_cpus(self, cpus, freq, exact=False): 346 """ 347 Set the frequency for the specified list of CPUs. 348 See https://www.kernel.org/doc/Documentation/cpu-freq/governors.txt 349 350 :param cpus: The list of CPU for which the frequency has to be set. 351 """ 352 for cpu in cpus: 353 self.set_frequency(cpu, freq, exact) 354 355 def set_all_frequencies(self, freq): 356 """ 357 Set the specified (minimum) frequency for all the (online) CPUs 358 """ 359 return self.target._execute_util( 360 'cpufreq_set_all_frequencies {}'.format(freq), 361 as_root=True) 362 363 def get_all_frequencies(self): 364 """ 365 Get the current frequency for all the (online) CPUs 366 """ 367 output = self.target._execute_util( 368 'cpufreq_get_all_frequencies', as_root=True) 369 frequencies = {} 370 for x in output.splitlines(): 371 kv = x.split(' ') 372 if kv[0] == '': 373 break 374 frequencies[kv[0]] = kv[1] 375 return frequencies 376 377 def set_all_governors(self, governor): 378 """ 379 Set the specified governor for all the (online) CPUs 380 """ 381 try: 382 return self.target._execute_util( 383 'cpufreq_set_all_governors {}'.format(governor), 384 as_root=True) 385 except TargetError as e: 386 if ("echo: I/O error" in str(e) or 387 "write error: Invalid argument" in str(e)): 388 389 cpus_unsupported = [c for c in self.target.list_online_cpus() 390 if governor not in self.list_governors(c)] 391 raise TargetError("Governor {} unsupported for CPUs {}".format( 392 governor, cpus_unsupported)) 393 else: 394 raise 395 396 def get_all_governors(self): 397 """ 398 Get the current governor for all the (online) CPUs 399 """ 400 output = self.target._execute_util( 401 'cpufreq_get_all_governors', as_root=True) 402 governors = {} 403 for x in output.splitlines(): 404 kv = x.split(' ') 405 if kv[0] == '': 406 break 407 governors[kv[0]] = kv[1] 408 return governors 409 410 def trace_frequencies(self): 411 """ 412 Report current frequencies on trace file 413 """ 414 return self.target._execute_util('cpufreq_trace_all_frequencies', as_root=True) 415 416 def get_affected_cpus(self, cpu): 417 """ 418 Get the online CPUs that share a frequency domain with the given CPU 419 """ 420 if isinstance(cpu, int): 421 cpu = 'cpu{}'.format(cpu) 422 423 sysfile = '/sys/devices/system/cpu/{}/cpufreq/affected_cpus'.format(cpu) 424 425 return [int(c) for c in self.target.read_value(sysfile).split()] 426 427 @memoized 428 def get_related_cpus(self, cpu): 429 """ 430 Get the CPUs that share a frequency domain with the given CPU 431 """ 432 if isinstance(cpu, int): 433 cpu = 'cpu{}'.format(cpu) 434 435 sysfile = '/sys/devices/system/cpu/{}/cpufreq/related_cpus'.format(cpu) 436 437 return [int(c) for c in self.target.read_value(sysfile).split()] 438 439 def iter_domains(self): 440 """ 441 Iterate over the frequency domains in the system 442 """ 443 cpus = set(range(self.target.number_of_cpus)) 444 while cpus: 445 cpu = iter(cpus).next() 446 domain = self.target.cpufreq.get_related_cpus(cpu) 447 yield domain 448 cpus = cpus.difference(domain) 449 450 def get_time_in_state(self, clusters): 451 """ 452 Gets the time at each frequency on each cluster 453 :param clusters: A list of clusters on the device. Each cluster is a 454 list of cpus on that cluster. 455 """ 456 time_in_state_by_cluster = {} 457 458 for i, cluster in enumerate(clusters): 459 frequencies = self.list_frequencies(cluster[0]) 460 time_in_state = dict((str(freq), 0) for freq in frequencies) 461 462 for cpu in cluster: 463 stats = self.target.execute('cat '\ 464 '/sys/devices/system/cpu/cpu{}/cpufreq/stats' 465 '/time_in_state'.format(cpu)) 466 for entry in stats.split('\n'): 467 if len(entry) > 0: 468 freq, time = entry.split(' ') 469 time_in_state[freq] += int(time) 470 471 time_in_state_by_cluster[str(i)] = time_in_state 472 473 return time_in_state_by_cluster 474 475 def dump_time_in_state_delta(self, start, clusters, dump_file): 476 """ 477 Dumps the time between the stop and start by cluster by frequency 478 :param start: The inital output from a call to cpufreq.get_time_in_state 479 :param clusters: A list of clusters on the device. Each cluster is a 480 list of cpus on that cluster. 481 :param dump_file: A file to dump the delta time_in_state_delta to. 482 """ 483 stop = self.get_time_in_state(clusters) 484 485 time_in_state_delta = {} 486 487 for cl in start: 488 time_in_state_delta[cl] = {} 489 490 for freq in start[cl].keys(): 491 time_in_start = start[cl][freq] 492 time_in_stop = stop[cl][freq] 493 time_in_state_delta[cl][freq] = time_in_stop - time_in_start 494 495 output = {'time_delta' : time_in_state_delta, 496 'clusters' : {str(i) : [str(c) for c in cl] 497 for i, cl in enumerate(clusters)}} 498 499 with open(dump_file, 'w') as dfile: 500 json.dump(output, dfile, indent=4, sort_keys=True) 501