Skip to main content

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