starnix_modules_thermal/
lib.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
5#![recursion_limit = "256"]
6
7mod cooling;
8mod family;
9mod thermal_zone;
10
11use crate::thermal_zone::{SensorProps, ThermalZone};
12use anyhow::{Error, anyhow};
13use family::ThermalFamily;
14use starnix_core::device::kobject::Device;
15use starnix_core::fs::sysfs::build_device_directory;
16use starnix_core::task::{CurrentTask, Kernel};
17use starnix_core::vfs::FsNodeOps;
18use starnix_core::vfs::pseudo::simple_directory::SimpleDirectoryMutator;
19use starnix_core::vfs::pseudo::simple_file::{BytesFile, BytesFileOps};
20use starnix_logging::{log_error, log_warn};
21use starnix_sync::{Locked, Unlocked};
22use starnix_uapi::errors::{Errno, errno, error};
23use starnix_uapi::file_mode::mode;
24use std::borrow::Cow;
25use std::collections::HashMap;
26use std::sync::Arc;
27use thermal_netlink::{celsius_to_millicelsius, millicelsius_to_celsius};
28use zx::MonotonicInstant;
29use {fidl_fuchsia_hardware_temperature as ftemperature, fidl_fuchsia_thermal as fthermal};
30
31pub use cooling::cooling_device_init;
32
33fn build_thermal_zone_directory(
34    device: &Device,
35    proxy: ftemperature::DeviceSynchronousProxy,
36    sensor_manager: fthermal::SensorManagerSynchronousProxy,
37    device_type: String,
38    dir: &SimpleDirectoryMutator,
39) {
40    build_device_directory(device, dir);
41    dir.entry(
42        "emul_temp",
43        EmulTempFile::new_node(device_type.clone(), sensor_manager),
44        mode!(IFREG, 0o200),
45    );
46    dir.entry("temp", TemperatureFile::new_node(proxy), mode!(IFREG, 0o664));
47    dir.entry(
48        "type",
49        BytesFile::new_node(format!("{}\n", device_type).into_bytes()),
50        mode!(IFREG, 0o444),
51    );
52    dir.entry("policy", BytesFile::new_node(b"step_wise\n".to_vec()), mode!(IFREG, 0o444));
53    dir.entry(
54        "available_policies",
55        BytesFile::new_node(b"step_wise\n".to_vec()),
56        mode!(IFREG, 0o444),
57    );
58}
59
60struct TemperatureFile {
61    proxy: ftemperature::DeviceSynchronousProxy,
62}
63
64impl TemperatureFile {
65    pub fn new_node(proxy: ftemperature::DeviceSynchronousProxy) -> impl FsNodeOps {
66        BytesFile::new_node(Self { proxy })
67    }
68}
69
70impl BytesFileOps for TemperatureFile {
71    fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
72        let (zx_status, temp) =
73            self.proxy.get_temperature_celsius(MonotonicInstant::INFINITE).map_err(|e| {
74                log_error!("get_temperature_celsius failed: {}", e);
75                errno!(ENOENT)
76            })?;
77        let _ = zx::Status::ok(zx_status).map_err(|e| {
78            log_error!("get_temperature_celsius driver returned error: {}", e);
79            errno!(ENOENT)
80        })?;
81
82        let out = format!("{}\n", celsius_to_millicelsius(temp) as i32);
83        Ok(out.as_bytes().to_owned().into())
84    }
85}
86
87struct EmulTempFile {
88    device_type: String,
89    proxy: fthermal::SensorManagerSynchronousProxy,
90}
91
92impl EmulTempFile {
93    pub fn new_node(
94        device_type: String,
95        proxy: fthermal::SensorManagerSynchronousProxy,
96    ) -> impl FsNodeOps {
97        BytesFile::new_node(Self { device_type, proxy })
98    }
99}
100
101impl BytesFileOps for EmulTempFile {
102    fn write(&self, _current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
103        let num_str = str::from_utf8(&data).map_err(|e| {
104            log_warn!("Failed to convert input temp to utf-8: {:?}", e);
105            errno!(EINVAL)
106        })?;
107
108        let temp_milli_c: i32 = num_str.trim().parse().map_err(|e| {
109            log_warn!("Failed to parse input temp as i32: {:?}", e);
110            errno!(EINVAL)
111        })?;
112
113        if temp_milli_c == 0 {
114            match self
115                .proxy
116                .clear_temperature_override(&self.device_type, zx::MonotonicInstant::INFINITE)
117            {
118                Ok(Ok(_)) => Ok(()),
119                Ok(Err(error)) => {
120                    log_warn!(
121                        "Failed to clear temperature override for sensor {}: {:?}",
122                        self.device_type,
123                        error
124                    );
125                    error!(EINVAL)
126                }
127                Err(error) => {
128                    log_warn!(
129                        "Failed to call clear_temperature_override for sensor {}: {:?}",
130                        self.device_type,
131                        error
132                    );
133                    error!(EIO)
134                }
135            }
136        } else {
137            match self.proxy.set_temperature_override(
138                &self.device_type,
139                millicelsius_to_celsius(temp_milli_c as f32).into(),
140                zx::MonotonicInstant::INFINITE,
141            ) {
142                Ok(Ok(_)) => Ok(()),
143                Ok(Err(error)) => {
144                    log_warn!(
145                        "Failed to set temperature override for sensor {}: {:?}",
146                        self.device_type,
147                        error
148                    );
149                    error!(EINVAL)
150                }
151                Err(error) => {
152                    log_warn!(
153                        "Failed to call set_temperature_override for sensor {}: {:?}",
154                        self.device_type,
155                        error
156                    );
157                    error!(EIO)
158                }
159            }
160        }
161    }
162}
163
164pub fn thermal_device_init(locked: &mut Locked<Unlocked>, kernel: &Kernel) -> Result<(), Error> {
165    let sensor_manager =
166        fuchsia_component::client::connect_to_protocol_sync::<fthermal::SensorManagerMarker>()
167            .map_err(|error| anyhow!("Failed to connect to SensorManager: {:?}", error))?;
168
169    let sensors = sensor_manager.list_sensors(zx::MonotonicInstant::INFINITE)?;
170
171    let registry = &kernel.device_registry;
172    let virtual_thermal_class = registry.objects.virtual_thermal_class();
173    let mut sensor_proxies = HashMap::new();
174
175    for (thermal_zone_id, sensor_info) in sensors.into_iter().enumerate() {
176        let Some(sensor_name) = sensor_info.name else {
177            log_warn!("No sensor name for thermal zone {}, skipping.", thermal_zone_id);
178            continue;
179        };
180        let sensor_name_clone = sensor_name.clone();
181
182        let thermal_zone_id = thermal_zone_id as u32;
183        let thermal_zone = format!("thermal_zone{}", thermal_zone_id);
184
185        // Create a synchronous proxy for file requests.
186        // Reads and writes are expected to block until data or an error is returned.
187        let (sensor_sync, sensor_server_sync) = fidl::endpoints::create_sync_proxy();
188
189        if let Err(error) = sensor_manager.connect(
190            fthermal::SensorManagerConnectRequest {
191                name: Some(sensor_name.clone()),
192                server_end: Some(fthermal::SensorServer_::Temperature(sensor_server_sync)),
193                ..Default::default()
194            },
195            zx::MonotonicInstant::INFINITE,
196        ) {
197            log_error!("Failed to connect to sensor {} (sync): {:?}", sensor_name, error);
198            continue;
199        }
200
201        registry.add_numberless_device(
202            locked,
203            thermal_zone.clone().as_str().into(),
204            virtual_thermal_class.clone(),
205            move |device, dir|{
206                match fuchsia_component::client::connect_to_protocol_sync::<fthermal::SensorManagerMarker>() {
207                    Ok(sensor_manager) => build_thermal_zone_directory(device, sensor_sync, sensor_manager, sensor_name_clone, dir),
208                    Err(error) => log_warn!("Failed to connect to SensorManager when building thermal zone for sensor {}: {:?}", sensor_name_clone, error),
209                }
210            },
211        );
212
213        // Create an asynchronous proxy for netlink calls.
214        // The thermal netlink server periodically gets temperature data from the thermal sensors in
215        // the background.
216        let (sensor, sensor_server) = fidl::endpoints::create_proxy();
217
218        if let Err(error) = sensor_manager.connect(
219            fthermal::SensorManagerConnectRequest {
220                name: Some(sensor_name.clone()),
221                server_end: Some(fthermal::SensorServer_::Temperature(sensor_server)),
222                ..Default::default()
223            },
224            zx::MonotonicInstant::INFINITE,
225        ) {
226            log_error!("Failed to connect to sensor {} (async): {:?}", sensor_name, error);
227            continue;
228        }
229
230        sensor_proxies.insert(
231            SensorProps { name: sensor_name },
232            ThermalZone { id: thermal_zone_id, proxy: sensor },
233        );
234    }
235
236    let (thermal_family, thermal_family_worker) = ThermalFamily::new(sensor_proxies);
237    kernel.generic_netlink().add_family(Arc::new(thermal_family));
238    kernel
239        .kthreads
240        .spawn_future(move || async move { thermal_family_worker.await }, "thermal_netlink_worker");
241
242    Ok(())
243}