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