1#!/usr/bin/python3
2
3# Copyright (C) 2022 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17"""Utility to read CPU configurations from config file or device.
18"""
19
20import os
21import sys
22
23class CpuSettings:
24  def __init__(self):
25    self.allcores = [] # core id (int)
26    self.onlines = [] # CPU cores online
27    self.governor = ""
28    self.governors = {} # key: policy, value: governor
29    self.cpusets = {} # key: name, value: [] core number
30
31  def __str__(self):
32    strs = []
33    strs.append("CpuSettings:{")
34    add_line_with_indentation(strs, "allcores:", 4)
35    strs.append(str(self.allcores))
36    add_line_with_indentation(strs, "onlines:", 4)
37    strs.append(str(self.onlines))
38    add_line_with_indentation(strs, "governor:", 4)
39    strs.append(str(self.governor))
40    add_line_with_indentation(strs, "governors:[", 4)
41    for k in self.governors:
42      add_line_with_indentation(strs, str(k), 8)
43      strs.append('=')
44      strs.append(str(self.governors[k]))
45    strs.append("]")
46    add_line_with_indentation(strs, "cpusets:[", 4)
47    for k in self.cpusets:
48      add_line_with_indentation(strs, str(k), 8)
49      strs.append('=')
50      strs.append(str(self.cpusets[k]))
51    strs.append("]")
52    strs.append("}\n")
53    return ''.join(strs)
54
55class CpuConfig:
56  def __init__(self):
57    self.allcores = [] # core id (int)
58    self.coreMaxFreqKHz = {} # key: core id (int), # value: max freq (int)
59    self.configs = {} # key: name, value: CpuSettings
60
61  def __str__(self):
62    strs = []
63    strs.append("CpuConfig:[")
64    add_line_with_indentation(strs, "allcores:", 2)
65    strs.append(str(self.allcores))
66    add_line_with_indentation(strs, "coreMaxFreqKHz:", 2)
67    strs.append(str(self.coreMaxFreqKHz))
68    for k in self.configs:
69      add_line_with_indentation(strs, str(k), 2)
70      strs.append(':')
71      strs.append(str(self.configs[k]))
72    strs.append("]")
73    return ''.join(strs)
74
75def parse_ints(word):
76  # valid inputs: 0-7, 0-1,4-7, 0,1
77  word = word.strip()
78  if word == "":
79    return []
80  values = []
81  pairs = word.split(',')
82  for pair in pairs:
83    min_max = pair.split('-')
84    if len(min_max) == 2:
85      min = int(min_max[0])
86      max = int(min_max[1])
87      if min >= max:
88        raise ValueError('min {} larger than max {}'.format(min, max))
89      for i in range(min, max + 1):
90        values.append(i)
91    else:
92      values.append(int(pair))
93  values.sort()
94  return values
95
96def parse_config(configFile):
97  lines = configFile.readlines()
98  config = CpuConfig()
99  allcores = []
100  current_settings = None
101  default_governor = None
102  i = -1
103  for line in lines:
104    i = i + 1
105    line = line.strip()
106    if len(line) == 0: # allows empty line
107      continue
108    if line[0] == '#': # comment
109      continue
110    try:
111      words = line.split(':')
112      if words[0] == "allcores":
113        allcores = parse_ints(words[1])
114        config.allcores = allcores
115      elif words[0] == "core_max_freq_khz":
116        pair = words[1].split('=')
117        if len(pair) != 2:
118          raise ValueError("wrong config: {}".format(words[1]))
119        cores = parse_ints(pair[0])
120        freq = int(pair[1])
121        for core in cores:
122          config.coreMaxFreqKHz[core] = freq
123      elif words[0] == "default_governor":
124        default_governor = words[1]
125      elif words[0] == "case":
126        current_settings = CpuSettings()
127        current_settings.allcores = allcores
128        current_settings.governor = default_governor
129        config.configs[words[1]] = current_settings
130      elif words[0] == "online":
131        current_settings.onlines = parse_ints(words[1])
132      elif words[0] == "offline":
133        current_settings.onlines.extend(allcores)
134        offlines = parse_ints(words[1])
135        for cpu in offlines:
136          current_settings.onlines.remove(cpu)
137      elif words[0] == "cpuset":
138        cpuset_pair = words[1].split('=')
139        if len(cpuset_pair) == 1:
140          current_settings.cpusets[""] = parse_ints(cpuset_pair[0])
141        else:
142          current_settings.cpusets[cpuset_pair[0]] = parse_ints(cpuset_pair[1])
143      elif words[0] == "governor":
144        current_settings.governor = words[1]
145      else:
146        raise ValueError("Unknown keyword {}".format(words[0]))
147    except Exception as e:
148      print("Cannot parse line {}: {}".format(i, line))
149      raise e
150
151  return config
152
153def get_script_dir():
154  return os.path.dirname(os.path.realpath(sys.argv[0]))
155
156def add_line_with_indentation(strs, msg="", spaces=1):
157  strs.append("\n" + spaces * " " + msg)
158