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" => register_cpu_domains(kernel, &mut registrar)?,
221            t => {
222                return Err(format_err!("Unknown cooling device: {t:?}"));
223            }
224        };
225    }
226
227    Ok(())
228}
229
230struct FccCoolingOps {
231    proxy: fbattery::ChargerSynchronousProxy,
232    max_charge_level: u32,
233    charge_level: LockDepMutex<u32, ThermalChargeLevelLock>,
234}
235
236impl FccCoolingOps {
237    fn new(
238        proxy: fbattery::ChargerSynchronousProxy,
239        max_charge_level: u32,
240        charge_level: u32,
241    ) -> Self {
242        Self { proxy, max_charge_level, charge_level: charge_level.into() }
243    }
244}
245
246impl CoolingOps for FccCoolingOps {
247    fn get_max_state(&self) -> u32 {
248        self.max_charge_level
249    }
250
251    fn get_state(&self) -> Result<u32, Errno> {
252        let locked_charge_level = self.charge_level.lock();
253        Ok(*locked_charge_level)
254    }
255
256    fn set_state(&self, state: u32) -> Result<(), Errno> {
257        let mut locked_charge_level = self.charge_level.lock();
258
259        // Attempting to set a charge level greater than the maximum results in 0 being set.
260        // This is based on observations of how this node behaves on Linux.
261        // See b/446016549#comment4 for details.
262        let charge_level = if state > self.max_charge_level {
263            log_warn!(
264                "FCC charge_level of {} exceeds {}; setting to 0",
265                state,
266                self.max_charge_level
267            );
268            0
269        } else {
270            state
271        };
272
273        // When the charge level goes to the maximum, disable charging. Otherwise, when dropping
274        // below the maximum, enable charging.
275        if charge_level == self.max_charge_level {
276            self.proxy
277                .enable(false, zx::MonotonicInstant::INFINITE)
278                .map_err(|e| errno!(EIO, e))?
279                .map_err(|e| errno!(EIO, e))?;
280        } else if *locked_charge_level == self.max_charge_level {
281            self.proxy
282                .enable(true, zx::MonotonicInstant::INFINITE)
283                .map_err(|e| errno!(EIO, e))?
284                .map_err(|e| errno!(EIO, e))?;
285        }
286
287        *locked_charge_level = charge_level;
288        Ok(())
289    }
290}
291
292fn register_fcc_device(
293    kernel: &Kernel,
294    registrar: &mut CoolingDeviceRegistrar,
295    param: &str,
296) -> Result<(), Error> {
297    let proxy = connect_to_battery_charger().context("Failed to connect to battery Charger")?;
298    let max_charge_level: u32 = param.parse().context("Invalid max_charge_level")?;
299    let ops = FccCoolingOps::new(proxy, max_charge_level, 0);
300
301    registrar.register(kernel, "fcc".to_string(), ops);
302    Ok(())
303}
304
305fn connect_to_battery_charger() -> Result<fbattery::ChargerSynchronousProxy, Error> {
306    // Attempt to manually locate the charger service instance. The instance name is not static, so
307    // we connect to the first one routed into the namespace.
308    // TODO(b/460242910): Simplify this process.
309    let mut dir = std::fs::read_dir(BATTERY_CHARGER_SERVICE_DIRECTORY)
310        .context("Failed to read ChargerService directory")?;
311    let entry = dir
312        .next()
313        .ok_or_else(|| anyhow::format_err!("Missing ChargerService instance"))?
314        .context("Unable to read ChargerService instance")?;
315    let path = entry
316        .path()
317        .join("device")
318        .into_os_string()
319        .into_string()
320        .map_err(|_| anyhow::format_err!("Failed to get device path"))?;
321
322    let (client_end, server_end) = zx::Channel::create();
323    fdio::service_connect(&path, server_end)?;
324    Ok(fbattery::ChargerSynchronousProxy::from_channel(client_end))
325}