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, ThermalChargeLevelLock};
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>(&mut self, kernel: &Kernel, device_type: String, ops: T) -> Device {
114        let device_registry = &kernel.device_registry;
115        let device_class = device_registry.objects.virtual_thermal_class();
116
117        let cooling_device =
118            Arc::new(CoolingDevice::<T> { device_id: self.get_next_id(), device_type, ops });
119
120        device_registry.add_numberless_device(
121            cooling_device.get_device_name().as_str().into(),
122            device_class,
123            |device, dir| cooling_device.build_device_dir(device, dir),
124        )
125    }
126}
127
128struct CpuCoolingOps {
129    domain_controller: Arc<fcpu::DomainControllerSynchronousProxy>,
130    domain_id: u64,
131    available_frequencies_hz: Vec<u64>,
132}
133
134impl CoolingOps for CpuCoolingOps {
135    fn get_max_state(&self) -> u32 {
136        (self.available_frequencies_hz.len() - 1) as u32
137    }
138    fn get_state(&self) -> Result<u32, Errno> {
139        let max_frequency_index = self
140            .domain_controller
141            .get_max_frequency(self.domain_id, MonotonicInstant::INFINITE)
142            .map_err(|e| errno!(EIO, anyhow!("Failed to send get_max_frequency call: {:?}", e)))?
143            .map_err(|e| errno!(EIO, anyhow!("Failed response from get_max_frequency: {:?}", e)))?;
144        Ok(max_frequency_index as u32)
145    }
146    fn set_state(&self, state: u32) -> Result<(), Errno> {
147        if state == 0 {
148            self.domain_controller
149                .clear_max_frequency(self.domain_id, MonotonicInstant::INFINITE)
150                .map_err(|e| {
151                    errno!(EIO, anyhow!("Failed to send clear_max_frequency call: {:?}", e))
152                })?
153                .map_err(|e| {
154                    errno!(EIO, anyhow!("Failed response from clear_max_frequency: {:?}", e))
155                })
156        } else {
157            self.domain_controller
158                .set_max_frequency(self.domain_id, state.into(), MonotonicInstant::INFINITE)
159                .map_err(|e| {
160                    errno!(EIO, anyhow!("Failed to send set_max_frequency call: {:?}", e))
161                })?
162                .map_err(|e| {
163                    errno!(EIO, anyhow!("Failed response from set_max_frequency: {:?}", e))
164                })
165        }
166    }
167}
168
169fn register_cpu_domains(
170    kernel: &Kernel,
171    registrar: &mut CoolingDeviceRegistrar,
172) -> Result<(), Error> {
173    let domain_controller = Arc::new(
174        fuchsia_component::client::connect_to_protocol_sync::<fcpu::DomainControllerMarker>()
175            .map_err(|error| anyhow!("Failed to connect to DomainController: {:?}", error))?,
176    );
177    let domains = domain_controller
178        .list_domains(MonotonicInstant::INFINITE)
179        .map_err(|e| anyhow!("list_domains failed: {}", e))?;
180
181    // Each domain is tunable, so expose each as a separate cooling device.
182    for domain in domains {
183        let device_type = domain.name.expect("name not provided");
184        let ops = CpuCoolingOps {
185            domain_controller: domain_controller.clone(),
186            domain_id: domain.id.expect("id not provided"),
187            available_frequencies_hz: domain
188                .available_frequencies_hz
189                .expect("available_frequencies_hz not provided"),
190        };
191        registrar.register(kernel, device_type, ops);
192    }
193    Ok(())
194}
195
196/// Initializes the cooling devices specified in the device list.
197///
198/// Device strings are of the form `type[=param]`. Not all device types support a parameter.
199///
200/// Supported devices:
201/// * `fcc=N`: Fast charge current, where N is the maximum charge level.
202pub fn cooling_device_init(kernel: &Kernel, devices: Vec<String>) -> Result<(), Error> {
203    let mut registrar = CoolingDeviceRegistrar::new();
204    for device_spec in devices.into_iter() {
205        let (device_type, device_param) = device_spec
206            .split_once('=')
207            .map_or_else(|| (device_spec.as_str(), None), |(t, p)| (t, Some(p)));
208        match device_type {
209            "fcc" => {
210                // TODO(b/460321934): Return errors rather than logging them.
211                if let Err(e) = register_fcc_device(
212                    kernel,
213                    &mut registrar,
214                    device_param
215                        .ok_or_else(|| format_err!("Missing parameter for 'fcc' cooling device"))?,
216                ) {
217                    log_error!("Failed to register 'fcc' cooling device: {e:?}");
218                }
219            }
220            "cpu" => {
221                // TODO(b/460321934): Return errors rather than logging them.
222                if let Err(e) = register_cpu_domains(kernel, &mut registrar) {
223                    log_error!("Failed to register 'cpu' cooling device: {e:?}");
224                }
225            }
226            t => {
227                return Err(format_err!("Unknown cooling device: {t:?}"));
228            }
229        };
230    }
231
232    Ok(())
233}
234
235struct FccCoolingOps {
236    proxy: fbattery::ChargerSynchronousProxy,
237    max_charge_level: u32,
238    charge_level: LockDepMutex<u32, ThermalChargeLevelLock>,
239}
240
241impl FccCoolingOps {
242    fn new(
243        proxy: fbattery::ChargerSynchronousProxy,
244        max_charge_level: u32,
245        charge_level: u32,
246    ) -> Self {
247        Self { proxy, max_charge_level, charge_level: charge_level.into() }
248    }
249}
250
251impl CoolingOps for FccCoolingOps {
252    fn get_max_state(&self) -> u32 {
253        self.max_charge_level
254    }
255
256    fn get_state(&self) -> Result<u32, Errno> {
257        let locked_charge_level = self.charge_level.lock();
258        Ok(*locked_charge_level)
259    }
260
261    fn set_state(&self, state: u32) -> Result<(), Errno> {
262        let mut locked_charge_level = self.charge_level.lock();
263
264        // Attempting to set a charge level greater than the maximum results in 0 being set.
265        // This is based on observations of how this node behaves on Linux.
266        // See b/446016549#comment4 for details.
267        let charge_level = if state > self.max_charge_level {
268            log_warn!(
269                "FCC charge_level of {} exceeds {}; setting to 0",
270                state,
271                self.max_charge_level
272            );
273            0
274        } else {
275            state
276        };
277
278        // When the charge level goes to the maximum, disable charging. Otherwise, when dropping
279        // below the maximum, enable charging.
280        if charge_level == self.max_charge_level {
281            self.proxy
282                .enable(false, zx::MonotonicInstant::INFINITE)
283                .map_err(|e| errno!(EIO, e))?
284                .map_err(|e| errno!(EIO, e))?;
285        } else if *locked_charge_level == self.max_charge_level {
286            self.proxy
287                .enable(true, zx::MonotonicInstant::INFINITE)
288                .map_err(|e| errno!(EIO, e))?
289                .map_err(|e| errno!(EIO, e))?;
290        }
291
292        *locked_charge_level = charge_level;
293        Ok(())
294    }
295}
296
297fn register_fcc_device(
298    kernel: &Kernel,
299    registrar: &mut CoolingDeviceRegistrar,
300    param: &str,
301) -> Result<(), Error> {
302    let proxy = connect_to_battery_charger().context("Failed to connect to battery Charger")?;
303    let max_charge_level: u32 = param.parse().context("Invalid max_charge_level")?;
304    let ops = FccCoolingOps::new(proxy, max_charge_level, 0);
305
306    registrar.register(kernel, "fcc".to_string(), ops);
307    Ok(())
308}
309
310fn connect_to_battery_charger() -> Result<fbattery::ChargerSynchronousProxy, Error> {
311    // Attempt to manually locate the charger service instance. The instance name is not static, so
312    // we connect to the first one routed into the namespace.
313    // TODO(b/460242910): Simplify this process.
314    let mut dir = std::fs::read_dir(BATTERY_CHARGER_SERVICE_DIRECTORY)
315        .context("Failed to read ChargerService directory")?;
316    let entry = dir
317        .next()
318        .ok_or_else(|| anyhow::format_err!("Missing ChargerService instance"))?
319        .context("Unable to read ChargerService instance")?;
320    let path = entry
321        .path()
322        .join("device")
323        .into_os_string()
324        .into_string()
325        .map_err(|_| anyhow::format_err!("Failed to get device path"))?;
326
327    let (client_end, server_end) = zx::Channel::create();
328    fdio::service_connect(&path, server_end)?;
329    Ok(fbattery::ChargerSynchronousProxy::from_channel(client_end))
330}