starnix_modules_thermal/
cooling.rs

1// Copyright 2025 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::{Context, Error, anyhow, format_err};
6use fidl::endpoints::SynchronousProxy;
7use starnix_core::device::kobject::Device;
8use starnix_core::fs::sysfs::build_device_directory;
9use starnix_core::task::{CurrentTask, Kernel};
10use starnix_core::vfs::FsNodeOps;
11use starnix_core::vfs::pseudo::simple_directory::SimpleDirectoryMutator;
12use starnix_core::vfs::pseudo::simple_file::{BytesFile, BytesFileOps};
13use starnix_logging::{log_error, log_warn};
14use starnix_sync::{Locked, Mutex, Unlocked};
15use starnix_uapi::errors::{Errno, errno};
16use starnix_uapi::file_mode::mode;
17use std::borrow::Cow;
18use std::sync::Arc;
19use zx::MonotonicInstant;
20use {fidl_fuchsia_power_battery as fbattery, fidl_fuchsia_power_cpu as fcpu};
21
22const BATTERY_CHARGER_SERVICE_DIRECTORY: &str = "/svc/fuchsia.power.battery.ChargerService";
23
24trait CoolingOps: Send + Sync + 'static {
25    fn get_max_state(&self) -> u32;
26    fn get_state(&self) -> Result<u32, Errno>;
27    fn set_state(&self, state: u32) -> Result<(), Errno>;
28}
29
30impl<T: CoolingOps> CoolingOps for Arc<T> {
31    fn get_max_state(&self) -> u32 {
32        self.as_ref().get_max_state()
33    }
34
35    fn get_state(&self) -> Result<u32, Errno> {
36        self.as_ref().get_state()
37    }
38
39    fn set_state(&self, state: u32) -> Result<(), Errno> {
40        self.as_ref().set_state(state)
41    }
42}
43
44struct CoolingDevice<T: CoolingOps> {
45    device_id: u32,
46    device_type: String,
47    ops: T,
48}
49
50impl<T: CoolingOps> CoolingDevice<T> {
51    fn get_device_name(&self) -> String {
52        format!("cooling_device{}", self.device_id)
53    }
54
55    fn build_device_dir(self: Arc<Self>, device: &Device, dir: &SimpleDirectoryMutator) {
56        build_device_directory(device, dir);
57        dir.entry(
58            "max_state",
59            BytesFile::new_node(format!("{}\n", self.ops.get_max_state()).into_bytes()),
60            mode!(IFREG, 0o444),
61        );
62        dir.entry(
63            "type",
64            BytesFile::new_node(format!("{}\n", self.device_type).into_bytes()),
65            mode!(IFREG, 0o444),
66        );
67        dir.entry("cur_state", CurStateFile::new_node(self), mode!(IFREG, 0o644));
68    }
69}
70
71/// Current state file, which proxies integral reads and writes to [`CoolingOps`].
72struct CurStateFile<T: CoolingOps> {
73    cooling_device: Arc<CoolingDevice<T>>,
74}
75
76impl<T: CoolingOps> CurStateFile<T> {
77    fn new_node(cooling_device: Arc<CoolingDevice<T>>) -> impl FsNodeOps {
78        BytesFile::new_node(Self { cooling_device })
79    }
80}
81
82impl<T: CoolingOps> BytesFileOps for CurStateFile<T> {
83    fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
84        let state = self.cooling_device.ops.get_state()?;
85        Ok(format!("{}\n", state).into_bytes().into())
86    }
87
88    fn write(&self, _current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
89        let input = str::from_utf8(&data).map_err(|_| errno!(EINVAL))?;
90        let input_num = input.trim().parse().map_err(|_| errno!(EINVAL))?;
91        self.cooling_device.ops.set_state(input_num)
92    }
93}
94
95/// Registrar for cooling devices, which are sequentially numbered.
96struct CoolingDeviceRegistrar {
97    next_id: u32,
98}
99
100impl CoolingDeviceRegistrar {
101    fn new() -> Self {
102        Self { next_id: 0 }
103    }
104
105    fn get_next_id(&mut self) -> u32 {
106        let id = self.next_id;
107        self.next_id += 1;
108        id
109    }
110
111    /// Register a device in the virtual thermal class.
112    fn register<T: CoolingOps>(
113        &mut self,
114        locked: &mut Locked<Unlocked>,
115        kernel: &Kernel,
116        device_type: String,
117        ops: T,
118    ) -> Device {
119        let device_registry = &kernel.device_registry;
120        let device_class = device_registry.objects.virtual_thermal_class();
121
122        let cooling_device =
123            Arc::new(CoolingDevice::<T> { device_id: self.get_next_id(), device_type, ops });
124
125        device_registry.add_numberless_device(
126            locked,
127            cooling_device.get_device_name().as_str().into(),
128            device_class,
129            |device, dir| cooling_device.build_device_dir(device, dir),
130        )
131    }
132}
133
134struct CpuCoolingOps {
135    domain_controller: Arc<fcpu::DomainControllerSynchronousProxy>,
136    domain_id: u64,
137    available_frequencies_hz: Vec<u64>,
138}
139
140impl CoolingOps for CpuCoolingOps {
141    fn get_max_state(&self) -> u32 {
142        (self.available_frequencies_hz.len() - 1) as u32
143    }
144    fn get_state(&self) -> Result<u32, Errno> {
145        let max_frequency_index = self
146            .domain_controller
147            .get_max_frequency(self.domain_id, MonotonicInstant::INFINITE)
148            .map_err(|e| errno!(EIO, anyhow!("Failed to send get_max_frequency call: {:?}", e)))?
149            .map_err(|e| errno!(EIO, anyhow!("Failed response from get_max_frequency: {:?}", e)))?;
150        Ok(max_frequency_index as u32)
151    }
152    fn set_state(&self, state: u32) -> Result<(), Errno> {
153        if state == 0 {
154            self.domain_controller
155                .clear_max_frequency(self.domain_id, MonotonicInstant::INFINITE)
156                .map_err(|e| {
157                    errno!(EIO, anyhow!("Failed to send clear_max_frequency call: {:?}", e))
158                })?
159                .map_err(|e| {
160                    errno!(EIO, anyhow!("Failed response from clear_max_frequency: {:?}", e))
161                })
162        } else {
163            self.domain_controller
164                .set_max_frequency(self.domain_id, state.into(), MonotonicInstant::INFINITE)
165                .map_err(|e| {
166                    errno!(EIO, anyhow!("Failed to send set_max_frequency call: {:?}", e))
167                })?
168                .map_err(|e| {
169                    errno!(EIO, anyhow!("Failed response from set_max_frequency: {:?}", e))
170                })
171        }
172    }
173}
174
175fn register_cpu_domains(
176    locked: &mut Locked<Unlocked>,
177    kernel: &Kernel,
178    registrar: &mut CoolingDeviceRegistrar,
179) -> Result<(), Error> {
180    let domain_controller = Arc::new(
181        fuchsia_component::client::connect_to_protocol_sync::<fcpu::DomainControllerMarker>()
182            .map_err(|error| anyhow!("Failed to connect to DomainController: {:?}", error))?,
183    );
184    let domains = domain_controller
185        .list_domains(MonotonicInstant::INFINITE)
186        .map_err(|e| anyhow!("list_domains failed: {}", e))?;
187
188    // Each domain is tunable, so expose each as a separate cooling device.
189    for domain in domains {
190        let device_type = domain.name.expect("name not provided");
191        let ops = CpuCoolingOps {
192            domain_controller: domain_controller.clone(),
193            domain_id: domain.id.expect("id not provided"),
194            available_frequencies_hz: domain
195                .available_frequencies_hz
196                .expect("available_frequencies_hz not provided"),
197        };
198        registrar.register(locked, kernel, device_type, ops);
199    }
200    Ok(())
201}
202
203/// Initializes the cooling devices specified in the device list.
204///
205/// Device strings are of the form `type[=param]`. Not all device types support a parameter.
206///
207/// Supported devices:
208/// * `fcc=N`: Fast charge current, where N is the maximum charge level.
209pub fn cooling_device_init(
210    locked: &mut Locked<Unlocked>,
211    kernel: &Kernel,
212    devices: Vec<String>,
213) -> Result<(), Error> {
214    let mut registrar = CoolingDeviceRegistrar::new();
215    for device_spec in devices.into_iter() {
216        let (device_type, device_param) = device_spec
217            .split_once('=')
218            .map_or_else(|| (device_spec.as_str(), None), |(t, p)| (t, Some(p)));
219        match device_type {
220            "fcc" => {
221                // TODO(b/460321934): Return errors rather than logging them.
222                if let Err(e) = register_fcc_device(
223                    locked,
224                    kernel,
225                    &mut registrar,
226                    device_param
227                        .ok_or_else(|| format_err!("Missing parameter for 'fcc' cooling device"))?,
228                ) {
229                    log_error!("Failed to register 'fcc' cooling device: {e:?}");
230                }
231            }
232            "cpu" => register_cpu_domains(locked, kernel, &mut registrar)?,
233            t => {
234                return Err(format_err!("Unknown cooling device: {t:?}"));
235            }
236        };
237    }
238
239    Ok(())
240}
241
242struct FccCoolingOps {
243    proxy: fbattery::ChargerSynchronousProxy,
244    max_charge_level: u32,
245    charge_level: Mutex<u32>,
246}
247
248impl FccCoolingOps {
249    fn new(
250        proxy: fbattery::ChargerSynchronousProxy,
251        max_charge_level: u32,
252        charge_level: u32,
253    ) -> Self {
254        Self { proxy, max_charge_level, charge_level: Mutex::new(charge_level) }
255    }
256}
257
258impl CoolingOps for FccCoolingOps {
259    fn get_max_state(&self) -> u32 {
260        self.max_charge_level
261    }
262
263    fn get_state(&self) -> Result<u32, Errno> {
264        let locked_charge_level = self.charge_level.lock();
265        Ok(*locked_charge_level)
266    }
267
268    fn set_state(&self, state: u32) -> Result<(), Errno> {
269        let mut locked_charge_level = self.charge_level.lock();
270
271        // Attempting to set a charge level greater than the maximum results in 0 being set.
272        // This is based on observations of how this node behaves on Linux.
273        // See b/446016549#comment4 for details.
274        let charge_level = if state > self.max_charge_level {
275            log_warn!(
276                "FCC charge_level of {} exceeds {}; setting to 0",
277                state,
278                self.max_charge_level
279            );
280            0
281        } else {
282            state
283        };
284
285        // When the charge level goes to the maximum, disable charging. Otherwise, when dropping
286        // below the maximum, enable charging.
287        if charge_level == self.max_charge_level {
288            self.proxy
289                .enable(false, zx::MonotonicInstant::INFINITE)
290                .map_err(|e| errno!(EIO, e))?
291                .map_err(|e| errno!(EIO, e))?;
292        } else if *locked_charge_level == self.max_charge_level {
293            self.proxy
294                .enable(true, zx::MonotonicInstant::INFINITE)
295                .map_err(|e| errno!(EIO, e))?
296                .map_err(|e| errno!(EIO, e))?;
297        }
298
299        *locked_charge_level = charge_level;
300        Ok(())
301    }
302}
303
304fn register_fcc_device(
305    locked: &mut Locked<Unlocked>,
306    kernel: &Kernel,
307    registrar: &mut CoolingDeviceRegistrar,
308    param: &str,
309) -> Result<(), Error> {
310    let proxy = connect_to_battery_charger().context("Failed to connect to battery Charger")?;
311    let max_charge_level: u32 = param.parse().context("Invalid max_charge_level")?;
312    let ops = FccCoolingOps::new(proxy, max_charge_level, 0);
313
314    registrar.register(locked, kernel, "fcc".to_string(), ops);
315    Ok(())
316}
317
318fn connect_to_battery_charger() -> Result<fbattery::ChargerSynchronousProxy, Error> {
319    // Attempt to manually locate the charger service instance. The instance name is not static, so
320    // we connect to the first one routed into the namespace.
321    // TODO(b/460242910): Simplify this process.
322    let mut dir = std::fs::read_dir(BATTERY_CHARGER_SERVICE_DIRECTORY)
323        .context("Failed to read ChargerService directory")?;
324    let entry = dir
325        .next()
326        .ok_or_else(|| anyhow::format_err!("Missing ChargerService instance"))?
327        .context("Unable to read ChargerService instance")?;
328    let path = entry
329        .path()
330        .join("device")
331        .into_os_string()
332        .into_string()
333        .map_err(|_| anyhow::format_err!("Failed to get device path"))?;
334
335    let (client_end, server_end) = zx::Channel::create();
336    fdio::service_connect(&path, server_end)?;
337    Ok(fbattery::ChargerSynchronousProxy::from_channel(client_end))
338}