energy_model_config/
lib.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use anyhow::{ensure, Context, Error};
6use serde_derive::Deserialize;
7use std::fs::File;
8use std::io::Read as _;
9use std::path::Path;
10use zx_types::{
11    zx_cpu_set_t, zx_processor_power_domain_t, zx_processor_power_level_t,
12    zx_processor_power_level_transition_t, ZX_CPU_SET_BITS_PER_WORD, ZX_CPU_SET_MAX_CPUS,
13    ZX_MAX_NAME_LEN, ZX_PROCESSOR_POWER_CONTROL_ARM_PSCI, ZX_PROCESSOR_POWER_CONTROL_ARM_WFI,
14    ZX_PROCESSOR_POWER_CONTROL_CPU_DRIVER, ZX_PROCESSOR_POWER_CONTROL_RISCV_SBI,
15    ZX_PROCESSOR_POWER_CONTROL_RISCV_WFI, ZX_PROCESSOR_POWER_LEVEL_OPTIONS_DOMAIN_INDEPENDENT,
16};
17
18/// This library is used to parse a processor energy model JSON file into a data structure which
19/// also implements some convenience methods for accessing and consuming the data.
20///
21/// The intended usage is that `EnergyModel::new()` is called with an energy model JSON file path.
22/// If successful, the function returns an EnergyModel instance containing the parsed config data.
23///
24/// The parser expects a JSON5 file of the following format:
25///     [
26///         {
27///             power_domain: {
28///                 cpu_set: [
29///                     1,
30///                     2,
31///                     3,
32///                 ],
33///                 domain_id: 0,
34///             },
35///             power_levels: [
36///                 {
37///                     processing_rate: 200,
38///                     power_coefficient_nw: 100,
39///                     control_interface: 'CpuDriver',
40///                     control_argument: 0,
41///                     diagnostic_name: 'Pstate 0',
42///                 },
43///                 {
44///                     processing_rate: 100,
45///                     power_coefficient_nw: 50,
46///                     control_interface: 'CpuDriver',
47///                     control_argument: 0,
48///                     diagnostic_name: 'Pstate 1',
49///                 },
50///             ],
51///             power_level_transitions: [
52///                 {
53///                     from: 0,
54///                     to: 1,
55///                     duration_ns: 100,
56///                     energy_nj: 100,
57///                 },
58///             ],
59///         },
60///         {
61///             power_domain: {
62///                 cpu_set: [
63///                     0,
64///                     4,
65///                 ],
66///                 domain_id: 1,
67///             },
68///             power_levels: {
69///                 option: 'DomainIndependent',
70///                 processing_rate: 200,
71///                 power_coefficient_nw: 200,
72///                 control_interface: 'CpuDriver',
73///                 control_argument: 0,
74///                 diagnostic_name: 'Pstate 0',
75///             },
76///             power_level_transitions: [
77///             ],
78///         },
79///     ]
80
81/// Defines a Json compatible energy model struct of a specific set of CPUs.
82#[derive(Deserialize, Debug, Clone, PartialEq)]
83pub struct PowerLevelDomainJson {
84    // Represents CPUs that are a subset of a domain.
85    pub power_domain: PowerDomain,
86
87    // Power levels are implicitly enumerated as the indices of `power_levels`.
88    pub power_levels: Vec<PowerLevel>,
89
90    // There are no ordering restrictions on `power_level_transitions` and the
91    // power levels they encode correspond to those defined by `power_levels`.
92    // An absent transition between levels is assumed to indicate that there
93    // is no energy or latency cost borne by performing it.
94    pub power_level_transitions: Vec<PowerLevelTransition>,
95}
96
97#[derive(Deserialize, Debug, Clone, PartialEq)]
98pub struct PowerDomain {
99    // A vector of CPU indexes.
100    pub cpu_set: Vec<u16>,
101    // A unique identifier for all CPUs in the cpu_set.
102    pub domain_id: u32,
103}
104
105#[derive(Deserialize, Debug, Clone, PartialEq)]
106pub struct PowerLevel {
107    pub option: Option<PowerLevelOption>,
108    pub processing_rate: u64,
109    pub power_coefficient_nw: u64,
110    pub control_interface: ControlInterface,
111    pub control_argument: u64,
112    pub diagnostic_name: String,
113}
114
115#[derive(Deserialize, Debug, Clone, PartialEq)]
116pub struct PowerLevelTransition {
117    pub from: u8,
118    pub to: u8,
119    pub duration_ns: i64,
120    pub energy_nj: u64,
121}
122
123#[derive(Deserialize, Debug, Clone, PartialEq)]
124pub enum ControlInterface {
125    CpuDriver,
126    ArmPsci,
127    ArmWfi,
128    RiscvSbi,
129    RiscvWfi,
130}
131
132#[derive(Deserialize, Debug, Clone, PartialEq)]
133pub enum PowerLevelOption {
134    DomainIndependent,
135}
136
137impl PowerLevelDomainJson {
138    pub fn validate(&self) -> Result<(), Error> {
139        let power_level_num = self.power_levels.len();
140        for power_level_transition in self.power_level_transitions.iter() {
141            let from_index = power_level_transition.from as usize;
142            let to_index = power_level_transition.to as usize;
143            ensure!(
144                from_index != to_index
145                    && from_index < power_level_num
146                    && to_index < power_level_num,
147                "Power level transition need to be between two different valid indexes",
148            );
149            let from_processing_rate = self.power_levels[from_index].processing_rate;
150            let to_processing_rate = self.power_levels[to_index].processing_rate;
151            ensure!(
152                from_processing_rate != 0 || to_processing_rate != 0,
153                "Transitions directly from one idle power level to another idle power level are invalid",
154            );
155        }
156        Ok(())
157    }
158}
159
160/// Represents the top level of an energy model structure.
161#[derive(Debug, Clone, PartialEq)]
162pub struct EnergyModel(pub Vec<PowerLevelDomain>);
163
164/// Zircon compatible version of PowerLevelDomainJson.
165#[derive(Debug, Clone, PartialEq)]
166pub struct PowerLevelDomain {
167    pub power_domain: zx_processor_power_domain_t,
168    pub power_levels: Vec<zx_processor_power_level_t>,
169    pub power_level_transitions: Vec<zx_processor_power_level_transition_t>,
170}
171
172impl EnergyModel {
173    pub fn new(json_file_path: &Path) -> Result<EnergyModel, Error> {
174        let mut buffer = String::new();
175        File::open(&json_file_path)?.read_to_string(&mut buffer)?;
176
177        let power_level_domains = serde_json5::from_str::<Vec<PowerLevelDomainJson>>(&buffer)?;
178
179        // Iterate and validate each underlying PowerLevelDomain instance.
180        for power_level_domain in power_level_domains.iter() {
181            power_level_domain.validate().context(format!(
182                "Validation failed for power_domain {}",
183                power_level_domain.power_domain.domain_id
184            ))?;
185        }
186
187        Ok(Self(
188            power_level_domains
189                .into_iter()
190                .map(|s| s.try_into())
191                .collect::<Result<Vec<PowerLevelDomain>, _>>()?,
192        ))
193    }
194}
195
196impl TryFrom<PowerLevelDomainJson> for PowerLevelDomain {
197    type Error = anyhow::Error;
198    fn try_from(e: PowerLevelDomainJson) -> Result<Self, Error> {
199        Ok(Self {
200            power_domain: e.power_domain.try_into()?,
201            power_levels: e
202                .power_levels
203                .into_iter()
204                .map(|s| s.try_into())
205                .collect::<Result<Vec<zx_processor_power_level_t>, _>>()?,
206            power_level_transitions: e
207                .power_level_transitions
208                .into_iter()
209                .map(|s| s.into())
210                .collect(),
211        })
212    }
213}
214
215impl TryFrom<PowerDomain> for zx_processor_power_domain_t {
216    type Error = anyhow::Error;
217    fn try_from(p: PowerDomain) -> Result<Self, Error> {
218        let mut ret = Self::default();
219        ret.cpus = get_cpu_mask(p.cpu_set)?;
220        ret.domain_id = p.domain_id;
221        Ok(ret)
222    }
223}
224
225impl From<PowerLevelTransition> for zx_processor_power_level_transition_t {
226    fn from(p: PowerLevelTransition) -> Self {
227        let mut ret = Self::default();
228        ret.from = p.from;
229        ret.to = p.to;
230        ret.latency = p.duration_ns;
231        ret.energy = p.energy_nj;
232        ret
233    }
234}
235
236impl TryFrom<PowerLevel> for zx_processor_power_level_t {
237    type Error = anyhow::Error;
238    fn try_from(p: PowerLevel) -> Result<Self, Error> {
239        let control_interface = match p.control_interface {
240            ControlInterface::CpuDriver => ZX_PROCESSOR_POWER_CONTROL_CPU_DRIVER,
241            ControlInterface::ArmPsci => ZX_PROCESSOR_POWER_CONTROL_ARM_PSCI,
242            ControlInterface::ArmWfi => ZX_PROCESSOR_POWER_CONTROL_ARM_WFI,
243            ControlInterface::RiscvSbi => ZX_PROCESSOR_POWER_CONTROL_RISCV_SBI,
244            ControlInterface::RiscvWfi => ZX_PROCESSOR_POWER_CONTROL_RISCV_WFI,
245        };
246
247        let options = match p.option {
248            Some(PowerLevelOption::DomainIndependent) => {
249                ZX_PROCESSOR_POWER_LEVEL_OPTIONS_DOMAIN_INDEPENDENT
250            }
251            _ => 0,
252        };
253
254        let bytes = p.diagnostic_name.as_bytes();
255        ensure!(
256            bytes.len() <= ZX_MAX_NAME_LEN,
257            "diagnostic_name {:?} must be shorter than {:?} bytes",
258            p.diagnostic_name,
259            ZX_MAX_NAME_LEN,
260        );
261        let mut diagnostic_name = [0u8; ZX_MAX_NAME_LEN];
262        diagnostic_name[..bytes.len()].copy_from_slice(bytes);
263
264        let mut ret = Self::default();
265        ret.options = options;
266        ret.processing_rate = p.processing_rate;
267        ret.power_coefficient_nw = p.power_coefficient_nw;
268        ret.control_interface = control_interface;
269        ret.control_argument = p.control_argument;
270        ret.diagnostic_name = diagnostic_name;
271        Ok(ret)
272    }
273}
274
275// Convert from a list of CPU indexes to CPU mask.
276//
277// Zircon definition:
278// The |N|'th CPU is considered in the CPU set if the bit:
279//
280//   cpu_mask[N / ZX_CPU_SET_BITS_PER_WORD]
281//       & (1 << (N % ZX_CPU_SET_BITS_PER_WORD))
282//
283// is set.
284fn get_cpu_mask(cpu_set: Vec<u16>) -> Result<zx_cpu_set_t, anyhow::Error> {
285    let mut cpu_mask = [0 as u64; ZX_CPU_SET_MAX_CPUS / ZX_CPU_SET_BITS_PER_WORD];
286    for cpu in cpu_set {
287        let cpu_index = cpu as usize;
288        ensure!(
289            cpu_index < ZX_CPU_SET_MAX_CPUS,
290            "CPU index must be smaller than {:?}",
291            ZX_CPU_SET_MAX_CPUS,
292        );
293
294        let i = cpu_index / ZX_CPU_SET_BITS_PER_WORD;
295        cpu_mask[i] = cpu_mask[i] | (1 << (cpu_index % ZX_CPU_SET_BITS_PER_WORD));
296    }
297    Ok(zx_cpu_set_t { mask: cpu_mask })
298}
299
300#[cfg(test)]
301mod tests {
302    use crate::*;
303    use assert_matches::assert_matches;
304
305    #[test]
306    fn test_convert_cpu_mask() {
307        assert_eq!(
308            get_cpu_mask(vec![0u16, 1u16, 2u16, 3u16]).unwrap().mask,
309            [15u64, 0u64, 0u64, 0u64, 0u64, 0u64, 0u64, 0u64]
310        );
311
312        fn is_nth_set(
313            cpu_mask: [u64; ZX_CPU_SET_MAX_CPUS / ZX_CPU_SET_BITS_PER_WORD],
314            n: usize,
315        ) -> bool {
316            cpu_mask[n / ZX_CPU_SET_BITS_PER_WORD] & (1 << (n % ZX_CPU_SET_BITS_PER_WORD)) != 0
317        }
318
319        let mut cpu_set = vec![15u16];
320        let mut cpu_mask = get_cpu_mask(cpu_set).unwrap().mask;
321        assert!(is_nth_set(cpu_mask, 15));
322
323        cpu_set = vec![32u16];
324        cpu_mask = get_cpu_mask(cpu_set).unwrap().mask;
325        assert!(is_nth_set(cpu_mask, 32));
326
327        // CPU index must be smaller than ZX_CPU_SET_MAX_CPUS.
328        cpu_set = vec![ZX_CPU_SET_MAX_CPUS as u16];
329        assert_matches!(get_cpu_mask(cpu_set), Err(_));
330    }
331
332    #[test]
333    fn test_processor_power_level() {
334        let power_level = PowerLevel {
335            option: Some(PowerLevelOption::DomainIndependent),
336            processing_rate: 0,
337            power_coefficient_nw: 0,
338            control_interface: ControlInterface::ArmPsci,
339            control_argument: 0,
340            diagnostic_name:
341                "This is a very very very long diagnositc name which exceeds 32 bytes.".to_string(),
342        };
343        assert_matches!(zx_processor_power_level_t::try_from(power_level), Err(_));
344
345        let mut zx_power_level = zx_processor_power_level_t::default();
346        zx_power_level.control_interface = ZX_PROCESSOR_POWER_CONTROL_CPU_DRIVER;
347        let power_level = PowerLevel {
348            option: None,
349            processing_rate: 0,
350            power_coefficient_nw: 0,
351            control_interface: ControlInterface::CpuDriver,
352            control_argument: 0,
353            diagnostic_name: "".to_string(),
354        };
355
356        assert!(zx_processor_power_level_t::try_from(power_level).unwrap() == zx_power_level);
357    }
358}