1#![recursion_limit = "256"]
6
7mod cooling;
8mod family;
9mod powercap;
10mod thermal_zone;
11
12use crate::thermal_zone::{SensorProps, ThermalZone};
13use anyhow::{Error, anyhow, bail};
14use cooling::cooling_device_init;
15use family::ThermalFamily;
16use fidl_fuchsia_hardware_temperature as ftemperature;
17use fidl_fuchsia_hardware_trippoint as ftrippoint;
18use fidl_fuchsia_thermal as fthermal;
19use powercap::powercap_device_init;
20use starnix_core::device::kobject::Device;
21use starnix_core::fs::sysfs::build_device_directory;
22use starnix_core::task::{ArgNameAndValue, CurrentTask, Kernel};
23use starnix_core::vfs::FsNodeOps;
24use starnix_core::vfs::pseudo::simple_directory::SimpleDirectoryMutator;
25use starnix_core::vfs::pseudo::simple_file::{BytesFile, BytesFileOps};
26use starnix_logging::{log_error, log_warn};
27
28use starnix_sync::{LockDepMutex, ThermalTripPointStateLock};
29use starnix_uapi::errors::{Errno, errno, error};
30use starnix_uapi::file_mode::mode;
31use std::borrow::Cow;
32use std::collections::HashMap;
33use std::sync::Arc;
34use thermal_netlink::{celsius_to_millicelsius, millicelsius_to_celsius};
35use zx::MonotonicInstant;
36
37fn build_thermal_zone_directory(
38 device: &Device,
39 proxy: ftemperature::DeviceSynchronousProxy,
40 trippoint_proxy: ftrippoint::TripPointSynchronousProxy,
41 sensor_manager: fthermal::SensorManagerSynchronousProxy,
42 device_type: String,
43 dir: &SimpleDirectoryMutator,
44) {
45 build_device_directory(device, dir);
46 let trip_point = Arc::new(ThermalZoneTripPoint::new(device_type.clone(), trippoint_proxy));
47 dir.entry(
48 "emul_temp",
49 EmulTempFile::new_node(device_type.clone(), sensor_manager),
50 mode!(IFREG, 0o200),
51 );
52 dir.entry("temp", TemperatureFile::new_node(proxy), mode!(IFREG, 0o664));
53 dir.entry(
54 "trip_point_0_temp",
55 TripPointTempFile::new_node(trip_point.clone()),
56 mode!(IFREG, 0o664),
57 );
58 dir.entry("trip_point_0_hyst", TripPointHystFile::new_node(trip_point), mode!(IFREG, 0o664));
59 dir.entry(
60 "type",
61 BytesFile::new_node(format!("{}\n", device_type).into_bytes()),
62 mode!(IFREG, 0o444),
63 );
64 dir.entry("policy", BytesFile::new_node(b"step_wise\n".to_vec()), mode!(IFREG, 0o444));
65 dir.entry(
66 "available_policies",
67 BytesFile::new_node(b"step_wise\n".to_vec()),
68 mode!(IFREG, 0o444),
69 );
70}
71
72struct TemperatureFile {
73 proxy: ftemperature::DeviceSynchronousProxy,
74}
75
76impl TemperatureFile {
77 pub fn new_node(proxy: ftemperature::DeviceSynchronousProxy) -> impl FsNodeOps {
78 BytesFile::new_node(Self { proxy })
79 }
80}
81
82impl BytesFileOps for TemperatureFile {
83 fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
84 let (zx_status, temp) =
85 self.proxy.get_temperature_celsius(MonotonicInstant::INFINITE).map_err(|e| {
86 log_error!("get_temperature_celsius failed: {}", e);
87 errno!(ENOENT)
88 })?;
89 let _ = zx::Status::ok(zx_status).map_err(|e| {
90 log_error!("get_temperature_celsius driver returned error: {}", e);
91 errno!(ENOENT)
92 })?;
93
94 let out = format!("{}\n", celsius_to_millicelsius(temp) as i32);
95 Ok(out.as_bytes().to_owned().into())
96 }
97}
98
99fn parse_i32_sysfs_input(data: &[u8], attr_name: &str) -> Result<i32, Errno> {
100 let num_str = str::from_utf8(data).map_err(|e| {
101 log_warn!("Failed to convert {} input to utf-8: {:?}", attr_name, e);
102 errno!(EINVAL)
103 })?;
104 num_str.trim().parse().map_err(|e| {
105 log_warn!("Failed to parse {} input as i32: {:?}", attr_name, e);
106 errno!(EINVAL)
107 })
108}
109
110struct EmulTempFile {
111 device_type: String,
112 proxy: fthermal::SensorManagerSynchronousProxy,
113}
114
115impl EmulTempFile {
116 pub fn new_node(
117 device_type: String,
118 proxy: fthermal::SensorManagerSynchronousProxy,
119 ) -> impl FsNodeOps {
120 BytesFile::new_node(Self { device_type, proxy })
121 }
122}
123
124impl BytesFileOps for EmulTempFile {
125 fn write(&self, _current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
126 let temp_milli_c = parse_i32_sysfs_input(&data, "emul_temp")?;
127
128 if temp_milli_c == 0 {
129 match self
130 .proxy
131 .clear_temperature_override(&self.device_type, zx::MonotonicInstant::INFINITE)
132 {
133 Ok(Ok(_)) => Ok(()),
134 Ok(Err(error)) => {
135 log_warn!(
136 "Failed to clear temperature override for sensor {}: {:?}",
137 self.device_type,
138 error
139 );
140 error!(EINVAL)
141 }
142 Err(error) => {
143 log_warn!(
144 "Failed to call clear_temperature_override for sensor {}: {:?}",
145 self.device_type,
146 error
147 );
148 error!(EIO)
149 }
150 }
151 } else {
152 match self.proxy.set_temperature_override(
153 &self.device_type,
154 millicelsius_to_celsius(temp_milli_c as f32).into(),
155 zx::MonotonicInstant::INFINITE,
156 ) {
157 Ok(Ok(_)) => Ok(()),
158 Ok(Err(error)) => {
159 log_warn!(
160 "Failed to set temperature override for sensor {}: {:?}",
161 self.device_type,
162 error
163 );
164 error!(EINVAL)
165 }
166 Err(error) => {
167 log_warn!(
168 "Failed to call set_temperature_override for sensor {}: {:?}",
169 self.device_type,
170 error
171 );
172 error!(EIO)
173 }
174 }
175 }
176 }
177}
178
179const TRIP_POINT_FIDL_TIMEOUT: zx::MonotonicDuration = zx::MonotonicDuration::from_seconds(5);
180
181fn trip_point_fidl_deadline() -> MonotonicInstant {
182 MonotonicInstant::after(TRIP_POINT_FIDL_TIMEOUT)
183}
184
185fn map_trip_point_fidl_error(err: &fidl::Error) -> Errno {
186 match err {
187 fidl::Error::ClientCall(zx::Status::TIMED_OUT)
188 | fidl::Error::ClientRead(fidl::TransportError::Status(zx::Status::TIMED_OUT))
189 | fidl::Error::ClientWrite(fidl::TransportError::Status(zx::Status::TIMED_OUT)) => {
190 errno!(ETIMEDOUT)
191 }
192 _ => errno!(EIO),
193 }
194}
195
196fn select_descriptor_index(
202 descriptors: &[ftrippoint::TripPointDescriptor],
203 target_type: ftrippoint::TripPointType,
204) -> Option<u32> {
205 descriptors.iter().rfind(|d| d.type_ == target_type).map(|d| d.index)
206}
207
208#[derive(Default)]
209struct TripPointState {
210 temp_mc: i32,
211 hyst_mc: i32,
212 cached_indices: Option<(u32, Option<u32>)>,
213}
214
215struct ThermalZoneTripPoint {
216 device_type: String,
217 proxy: ftrippoint::TripPointSynchronousProxy,
218 state: LockDepMutex<TripPointState, ThermalTripPointStateLock>,
219}
220
221impl ThermalZoneTripPoint {
222 fn new(device_type: String, proxy: ftrippoint::TripPointSynchronousProxy) -> Self {
223 Self { device_type, proxy, state: LockDepMutex::new(TripPointState::default()) }
224 }
225
226 fn temp_mc(&self) -> i32 {
227 self.state.lock().temp_mc
228 }
229
230 fn set_temp_mc(&self, temp_mc: i32) -> Result<(), Errno> {
231 let mut state = self.state.lock();
232 let hyst_mc = state.hyst_mc;
233 self.apply_trip_points(&mut state, (temp_mc != 0).then_some(temp_mc), hyst_mc)?;
234 state.temp_mc = temp_mc;
235 Ok(())
236 }
237
238 fn hyst_mc(&self) -> i32 {
239 self.state.lock().hyst_mc
240 }
241
242 fn set_hyst_mc(&self, hyst_mc: i32) -> Result<(), Errno> {
243 if hyst_mc < 0 {
244 return error!(EINVAL);
245 }
246
247 let mut state = self.state.lock();
248 let temp_mc = state.temp_mc;
249 if temp_mc != 0 {
250 self.apply_trip_points(&mut state, Some(temp_mc), hyst_mc)?;
251 } else {
252 let _ = self.resolve_descriptor_indices(&mut state)?;
257 }
258 state.hyst_mc = hyst_mc;
259 Ok(())
260 }
261
262 fn resolve_descriptor_indices(
263 &self,
264 state: &mut TripPointState,
265 ) -> Result<(u32, Option<u32>), Errno> {
266 if let Some(indices) = state.cached_indices {
267 return Ok(indices);
268 }
269
270 let descriptors = match self.proxy.get_trip_point_descriptors(trip_point_fidl_deadline()) {
271 Ok(Ok(descriptors)) => descriptors,
272 Ok(Err(status)) => {
273 log_warn!(
274 "get_trip_point_descriptors returned status {} for sensor {}",
275 zx::Status::err_from_raw(status),
276 self.device_type
277 );
278 return error!(EIO);
279 }
280 Err(err) => {
281 log_warn!(
282 "Failed to call get_trip_point_descriptors for sensor {}: {:?}",
283 self.device_type,
284 err
285 );
286 return Err(map_trip_point_fidl_error(&err));
287 }
288 };
289
290 let Some(high_index) =
291 select_descriptor_index(&descriptors, ftrippoint::TripPointType::OneshotTempAbove)
292 else {
293 log_warn!(
294 "No ONESHOT_TEMP_ABOVE trip point descriptor found for sensor {}",
295 self.device_type
296 );
297 return error!(EIO);
298 };
299 let low_index =
300 select_descriptor_index(&descriptors, ftrippoint::TripPointType::OneshotTempBelow);
301
302 let indices = (high_index, low_index);
303 state.cached_indices = Some(indices);
304 Ok(indices)
305 }
306
307 fn apply_trip_points(
308 &self,
309 state: &mut TripPointState,
310 temp_mc: Option<i32>,
311 hyst_mc: i32,
312 ) -> Result<(), Errno> {
313 let (high_index, low_index) = self.resolve_descriptor_indices(state)?;
314
315 let mut updates = Vec::with_capacity(2);
316 if let Some(temp_mc) = temp_mc {
317 let high_temp_c = millicelsius_to_celsius(temp_mc as f32);
318 updates.push(ftrippoint::TripPointDescriptor {
319 index: high_index,
320 type_: ftrippoint::TripPointType::OneshotTempAbove,
321 configuration: ftrippoint::TripPointValue::OneshotTempAboveTripPoint(
322 ftrippoint::OneshotTempAboveTripPoint {
323 critical_temperature_celsius: high_temp_c,
324 },
325 ),
326 });
327 if let Some(low_index) = low_index {
334 let low_temp_mc = temp_mc.saturating_sub(hyst_mc);
335 let low_temp_c = millicelsius_to_celsius(low_temp_mc as f32);
336 updates.push(ftrippoint::TripPointDescriptor {
337 index: low_index,
338 type_: ftrippoint::TripPointType::OneshotTempBelow,
339 configuration: ftrippoint::TripPointValue::OneshotTempBelowTripPoint(
340 ftrippoint::OneshotTempBelowTripPoint {
341 critical_temperature_celsius: low_temp_c,
342 },
343 ),
344 });
345 }
346 } else {
347 updates.push(ftrippoint::TripPointDescriptor {
348 index: high_index,
349 type_: ftrippoint::TripPointType::OneshotTempAbove,
350 configuration: ftrippoint::TripPointValue::ClearedTripPoint(
351 ftrippoint::ClearedTripPoint,
352 ),
353 });
354 if let Some(low_index) = low_index {
355 updates.push(ftrippoint::TripPointDescriptor {
356 index: low_index,
357 type_: ftrippoint::TripPointType::OneshotTempBelow,
358 configuration: ftrippoint::TripPointValue::ClearedTripPoint(
359 ftrippoint::ClearedTripPoint,
360 ),
361 });
362 }
363 }
364
365 match self.proxy.set_trip_points(&updates, trip_point_fidl_deadline()) {
366 Ok(Ok(())) => Ok(()),
367 Ok(Err(raw_status)) => {
368 let status = zx::Status::err_from_raw(raw_status);
369 log_warn!(
370 "set_trip_points returned status {} for sensor {}",
371 status,
372 self.device_type
373 );
374 match status {
375 zx::Status::INVALID_ARGS | zx::Status::OUT_OF_RANGE => error!(EINVAL),
376 _ => error!(EIO),
377 }
378 }
379 Err(err) => {
380 log_warn!(
381 "Failed to call set_trip_points for sensor {}: {:?}",
382 self.device_type,
383 err
384 );
385 Err(map_trip_point_fidl_error(&err))
386 }
387 }
388 }
389}
390
391struct TripPointTempFile {
392 trip_point: Arc<ThermalZoneTripPoint>,
393}
394
395impl TripPointTempFile {
396 pub fn new_node(trip_point: Arc<ThermalZoneTripPoint>) -> impl FsNodeOps {
397 BytesFile::new_node(Self { trip_point })
398 }
399}
400
401impl BytesFileOps for TripPointTempFile {
402 fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
403 let temp_mc = self.trip_point.temp_mc();
404 Ok(format!("{}\n", temp_mc).into_bytes().into())
405 }
406
407 fn write(&self, _current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
408 let temp_mc = parse_i32_sysfs_input(&data, "trip_point_0_temp")?;
409 self.trip_point.set_temp_mc(temp_mc)
410 }
411}
412
413struct TripPointHystFile {
414 trip_point: Arc<ThermalZoneTripPoint>,
415}
416
417impl TripPointHystFile {
418 pub fn new_node(trip_point: Arc<ThermalZoneTripPoint>) -> impl FsNodeOps {
419 BytesFile::new_node(Self { trip_point })
420 }
421}
422
423impl BytesFileOps for TripPointHystFile {
424 fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
425 let hyst_mc = self.trip_point.hyst_mc();
426 Ok(format!("{}\n", hyst_mc).into_bytes().into())
427 }
428
429 fn write(&self, _current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
430 let hyst_mc = parse_i32_sysfs_input(&data, "trip_point_0_hyst")?;
431 self.trip_point.set_hyst_mc(hyst_mc)
432 }
433}
434
435fn thermal_device_init(kernel: &Kernel) -> Result<(), Error> {
436 let sensor_manager =
437 fuchsia_component::client::connect_to_protocol_sync::<fthermal::SensorManagerMarker>()
438 .map_err(|error| anyhow!("Failed to connect to SensorManager: {:?}", error))?;
439
440 let sensors = sensor_manager.list_sensors(zx::MonotonicInstant::INFINITE)?;
441
442 let registry = &kernel.device_registry;
443 let thermal_class = registry.objects.thermal_class();
444 let mut sensor_proxies = HashMap::new();
445
446 for (thermal_zone_id, sensor_info) in sensors.into_iter().enumerate() {
447 let Some(sensor_name) = sensor_info.name else {
448 log_warn!("No sensor name for thermal zone {}, skipping.", thermal_zone_id);
449 continue;
450 };
451 let sensor_name_clone = sensor_name.clone();
452
453 let thermal_zone_id = thermal_zone_id as u32;
454 let thermal_zone = format!("thermal_zone{}", thermal_zone_id);
455
456 let (sensor_sync, sensor_server_sync) = fidl::endpoints::create_sync_proxy();
459
460 match sensor_manager.connect(
461 fthermal::SensorManagerConnectRequest {
462 name: Some(sensor_name.clone()),
463 server_end: Some(fthermal::SensorServer_::Temperature(sensor_server_sync)),
464 ..Default::default()
465 },
466 zx::MonotonicInstant::INFINITE,
467 ) {
468 Ok(Ok(())) => {}
469 Ok(Err(connect_err)) => {
470 log_error!("Failed to connect to sensor {} (sync): {:?}", sensor_name, connect_err);
471 continue;
472 }
473 Err(fidl_err) => {
474 log_error!(
475 "Failed to call connect for sensor {} (sync): {:?}",
476 sensor_name,
477 fidl_err
478 );
479 continue;
480 }
481 }
482
483 let (trippoint_sync, trippoint_server_sync) = fidl::endpoints::create_sync_proxy();
484
485 match sensor_manager.connect(
486 fthermal::SensorManagerConnectRequest {
487 name: Some(sensor_name.clone()),
488 server_end: Some(fthermal::SensorServer_::Trippoint(trippoint_server_sync)),
489 ..Default::default()
490 },
491 zx::MonotonicInstant::INFINITE,
492 ) {
493 Ok(Ok(())) => {}
494 Ok(Err(connect_err)) => {
495 log_warn!(
496 "Failed to connect to sensor {} trip point server (sync): {:?}",
497 sensor_name,
498 connect_err
499 );
500 }
501 Err(fidl_err) => {
502 log_warn!(
503 "Failed to call connect for sensor {} trip point server (sync): {:?}",
504 sensor_name,
505 fidl_err
506 );
507 }
508 }
509
510 registry.add_numberless_device(
511 thermal_zone.as_str().into(),
512 None,
513 thermal_class.clone(),
514 move |device, dir| {
515 match fuchsia_component::client::connect_to_protocol_sync::<fthermal::SensorManagerMarker>() {
516 Ok(sensor_manager) => build_thermal_zone_directory(device, sensor_sync, trippoint_sync, sensor_manager, sensor_name_clone, dir),
517 Err(error) => log_warn!("Failed to connect to SensorManager when building thermal zone for sensor {}: {:?}", sensor_name_clone, error),
518 }
519 },
520 );
521
522 let (sensor, sensor_server) = fidl::endpoints::create_proxy();
526
527 match sensor_manager.connect(
528 fthermal::SensorManagerConnectRequest {
529 name: Some(sensor_name.clone()),
530 server_end: Some(fthermal::SensorServer_::Temperature(sensor_server)),
531 ..Default::default()
532 },
533 zx::MonotonicInstant::INFINITE,
534 ) {
535 Ok(Ok(())) => {}
536 Ok(Err(connect_err)) => {
537 log_error!(
538 "Failed to connect to sensor {} (async): {:?}",
539 sensor_name,
540 connect_err
541 );
542 continue;
543 }
544 Err(fidl_err) => {
545 log_error!(
546 "Failed to call connect for sensor {} (async): {:?}",
547 sensor_name,
548 fidl_err
549 );
550 continue;
551 }
552 }
553
554 sensor_proxies.insert(
555 SensorProps { name: sensor_name },
556 ThermalZone { id: thermal_zone_id, proxy: sensor },
557 );
558 }
559
560 let (thermal_family, thermal_family_worker) = ThermalFamily::new(sensor_proxies);
561 kernel.generic_netlink().add_family(Arc::new(thermal_family));
562 kernel
563 .kthreads
564 .spawn_future(move || async move { thermal_family_worker.await }, "thermal_netlink_worker");
565
566 Ok(())
567}
568
569fn parse_args(args: &[&str]) -> Result<Vec<String>, Error> {
570 let mut cooling_devices = Vec::new();
571 for &arg in args {
572 match ArgNameAndValue::from(arg) {
573 ArgNameAndValue { name: "cooling", value: Some(cooling) } if !cooling.is_empty() => {
574 cooling_devices = cooling.split(',').map(String::from).collect();
575 }
576 _ => bail!("invalid thermal argument: {arg}"),
577 }
578 }
579 Ok(cooling_devices)
580}
581
582pub fn validate_args(args: &[&str]) -> Result<(), Error> {
584 parse_args(args).map(|_| ())
585}
586
587pub fn module_init(kernel: &Kernel, args: &[&str]) -> Result<(), Error> {
590 let cooling_devices = parse_args(args)?;
591 powercap_device_init(kernel);
592 thermal_device_init(kernel)?;
593 cooling_device_init(kernel, cooling_devices)
594}
595
596#[cfg(test)]
597mod tests {
598 use super::*;
599 use fuchsia_async as fasync;
600 use futures::TryStreamExt;
601 use std::sync::Mutex;
602 use std::sync::atomic::{AtomicUsize, Ordering};
603
604 #[fuchsia::test]
605 fn test_select_descriptor_index() {
606 let descriptors = vec![
607 ftrippoint::TripPointDescriptor {
608 index: 0,
609 type_: ftrippoint::TripPointType::OneshotTempAbove,
610 configuration: ftrippoint::TripPointValue::OneshotTempAboveTripPoint(
611 ftrippoint::OneshotTempAboveTripPoint { critical_temperature_celsius: 95.0 },
612 ),
613 },
614 ftrippoint::TripPointDescriptor {
615 index: 1,
616 type_: ftrippoint::TripPointType::OneshotTempAbove,
617 configuration: ftrippoint::TripPointValue::ClearedTripPoint(
618 ftrippoint::ClearedTripPoint,
619 ),
620 },
621 ftrippoint::TripPointDescriptor {
622 index: 2,
623 type_: ftrippoint::TripPointType::OneshotTempAbove,
624 configuration: ftrippoint::TripPointValue::ClearedTripPoint(
625 ftrippoint::ClearedTripPoint,
626 ),
627 },
628 ftrippoint::TripPointDescriptor {
629 index: 3,
630 type_: ftrippoint::TripPointType::OneshotTempBelow,
631 configuration: ftrippoint::TripPointValue::OneshotTempBelowTripPoint(
632 ftrippoint::OneshotTempBelowTripPoint { critical_temperature_celsius: 80.0 },
633 ),
634 },
635 ftrippoint::TripPointDescriptor {
636 index: 4,
637 type_: ftrippoint::TripPointType::OneshotTempBelow,
638 configuration: ftrippoint::TripPointValue::OneshotTempBelowTripPoint(
639 ftrippoint::OneshotTempBelowTripPoint { critical_temperature_celsius: 70.0 },
640 ),
641 },
642 ];
643
644 assert_eq!(
646 select_descriptor_index(&descriptors, ftrippoint::TripPointType::OneshotTempAbove),
647 Some(2)
648 );
649 assert_eq!(
650 select_descriptor_index(&descriptors[..2], ftrippoint::TripPointType::OneshotTempAbove),
651 Some(1)
652 );
653 assert_eq!(
654 select_descriptor_index(&descriptors, ftrippoint::TripPointType::OneshotTempBelow),
655 Some(4)
656 );
657
658 let all_cleared_startup = vec![
663 ftrippoint::TripPointDescriptor {
664 index: 0,
665 type_: ftrippoint::TripPointType::OneshotTempAbove,
666 configuration: ftrippoint::TripPointValue::ClearedTripPoint(
667 ftrippoint::ClearedTripPoint,
668 ),
669 },
670 ftrippoint::TripPointDescriptor {
671 index: 1,
672 type_: ftrippoint::TripPointType::OneshotTempAbove,
673 configuration: ftrippoint::TripPointValue::ClearedTripPoint(
674 ftrippoint::ClearedTripPoint,
675 ),
676 },
677 ftrippoint::TripPointDescriptor {
678 index: 2,
679 type_: ftrippoint::TripPointType::OneshotTempBelow,
680 configuration: ftrippoint::TripPointValue::ClearedTripPoint(
681 ftrippoint::ClearedTripPoint,
682 ),
683 },
684 ];
685 assert_eq!(
686 select_descriptor_index(
687 &all_cleared_startup,
688 ftrippoint::TripPointType::OneshotTempAbove
689 ),
690 Some(1)
691 );
692 assert_eq!(
693 select_descriptor_index(
694 &all_cleared_startup,
695 ftrippoint::TripPointType::OneshotTempBelow
696 ),
697 Some(2)
698 );
699
700 let warning_already_armed = vec![
701 ftrippoint::TripPointDescriptor {
702 index: 0,
703 type_: ftrippoint::TripPointType::OneshotTempAbove,
704 configuration: ftrippoint::TripPointValue::ClearedTripPoint(
705 ftrippoint::ClearedTripPoint,
706 ),
707 },
708 ftrippoint::TripPointDescriptor {
709 index: 1,
710 type_: ftrippoint::TripPointType::OneshotTempAbove,
711 configuration: ftrippoint::TripPointValue::OneshotTempAboveTripPoint(
712 ftrippoint::OneshotTempAboveTripPoint { critical_temperature_celsius: 85.0 },
713 ),
714 },
715 ];
716 assert_eq!(
717 select_descriptor_index(
718 &warning_already_armed,
719 ftrippoint::TripPointType::OneshotTempAbove
720 ),
721 Some(1)
722 );
723
724 assert_eq!(
726 select_descriptor_index(&descriptors[..3], ftrippoint::TripPointType::OneshotTempBelow),
727 None
728 );
729 assert_eq!(select_descriptor_index(&[], ftrippoint::TripPointType::OneshotTempAbove), None);
730 }
731
732 #[fuchsia::test]
733 async fn test_thermal_zone_trip_point_caching_and_errors() {
734 let scope = fasync::Scope::new();
735 let (client_end, mut stream) =
736 fidl::endpoints::create_request_stream::<ftrippoint::TripPointMarker>();
737 let get_descriptors_calls = Arc::new(AtomicUsize::new(0));
738 let get_descriptors_calls_clone = get_descriptors_calls.clone();
739
740 scope.spawn(async move {
744 while let Ok(Some(req)) = stream.try_next().await {
745 match req {
746 ftrippoint::TripPointRequest::GetTripPointDescriptors { responder } => {
747 get_descriptors_calls_clone.fetch_add(1, Ordering::SeqCst);
748 let _ = responder.send(Ok(&[ftrippoint::TripPointDescriptor {
749 index: 1,
750 type_: ftrippoint::TripPointType::OneshotTempAbove,
751 configuration: ftrippoint::TripPointValue::ClearedTripPoint(
752 ftrippoint::ClearedTripPoint,
753 ),
754 }]));
755 }
756 ftrippoint::TripPointRequest::SetTripPoints { descriptors, responder } => {
757 assert_eq!(descriptors.len(), 1);
758 assert_eq!(descriptors[0].index, 1);
759 assert_eq!(
760 descriptors[0].type_,
761 ftrippoint::TripPointType::OneshotTempAbove
762 );
763 let status = match &descriptors[0].configuration {
764 ftrippoint::TripPointValue::ClearedTripPoint(_) => Ok(()),
765 ftrippoint::TripPointValue::OneshotTempAboveTripPoint(tp) => {
766 if tp.critical_temperature_celsius > 120.0 {
767 Err(zx::Status::INVALID_ARGS.into_raw())
768 } else if tp.critical_temperature_celsius <= -273.15 {
769 Err(zx::Status::OUT_OF_RANGE.into_raw())
770 } else if (tp.critical_temperature_celsius - 100.0).abs() < 0.01 {
771 Err(zx::Status::INTERNAL.into_raw())
772 } else {
773 Ok(())
774 }
775 }
776 _ => Err(zx::Status::INVALID_ARGS.into_raw()),
777 };
778 let _ = responder.send(status);
779 }
780 _ => unreachable!(),
781 }
782 }
783 });
784
785 let get_descriptors_calls_check = get_descriptors_calls.clone();
786 fasync::unblock(move || {
787 let proxy = ftrippoint::TripPointSynchronousProxy::new(client_end.into_channel());
788 let trip_point = ThermalZoneTripPoint::new("test-sensor".to_string(), proxy);
789
790 assert_eq!(trip_point.set_hyst_mc(-1), error!(EINVAL));
792 assert_eq!(get_descriptors_calls_check.load(Ordering::SeqCst), 0);
793
794 assert_eq!(trip_point.set_hyst_mc(2_000), Ok(()));
797 assert_eq!(trip_point.hyst_mc(), 2_000);
798 assert_eq!(trip_point.state.lock().cached_indices, Some((1, None)));
799 assert_eq!(get_descriptors_calls_check.load(Ordering::SeqCst), 1);
800
801 assert_eq!(trip_point.set_temp_mc(75_000), Ok(()));
804 assert_eq!(trip_point.temp_mc(), 75_000);
805 assert_eq!(trip_point.set_hyst_mc(3_000), Ok(()));
806 assert_eq!(trip_point.hyst_mc(), 3_000);
807 assert_eq!(trip_point.set_temp_mc(0), Ok(()));
808 assert_eq!(trip_point.temp_mc(), 0);
809 assert_eq!(get_descriptors_calls_check.load(Ordering::SeqCst), 1);
810
811 assert_eq!(trip_point.set_temp_mc(80_000), Ok(()));
814 assert_eq!(trip_point.set_temp_mc(150_000), error!(EINVAL));
815 assert_eq!(trip_point.temp_mc(), 80_000);
816 assert_eq!(trip_point.set_temp_mc(-300_000), error!(EINVAL));
817 assert_eq!(trip_point.temp_mc(), 80_000);
818 assert_eq!(trip_point.set_temp_mc(100_000), error!(EIO));
819 assert_eq!(trip_point.temp_mc(), 80_000);
820 })
821 .await;
822 }
823
824 #[fuchsia::test]
825 async fn test_thermal_zone_trip_point_dual_descriptors_and_hyst_rollback() {
826 assert_eq!(parse_i32_sysfs_input(b" 75000\n", "trip_point_0_temp"), Ok(75_000));
827 assert_eq!(parse_i32_sysfs_input(b"-5000\n", "emul_temp"), Ok(-5_000));
828 assert_eq!(parse_i32_sysfs_input(b"invalid\n", "trip_point_0_temp"), error!(EINVAL));
829 assert_eq!(parse_i32_sysfs_input(&[0xff, 0xfe], "trip_point_0_hyst"), error!(EINVAL));
830
831 let scope = fasync::Scope::new();
832 let (client_end, mut stream) =
833 fidl::endpoints::create_request_stream::<ftrippoint::TripPointMarker>();
834 let recorded_batches = Arc::new(Mutex::new(Vec::new()));
835 let recorded_batches_clone = recorded_batches.clone();
836
837 scope.spawn(async move {
840 while let Ok(Some(req)) = stream.try_next().await {
841 match req {
842 ftrippoint::TripPointRequest::GetTripPointDescriptors { responder } => {
843 let _ = responder.send(Ok(&[
844 ftrippoint::TripPointDescriptor {
845 index: 0,
846 type_: ftrippoint::TripPointType::OneshotTempAbove,
847 configuration: ftrippoint::TripPointValue::ClearedTripPoint(
848 ftrippoint::ClearedTripPoint,
849 ),
850 },
851 ftrippoint::TripPointDescriptor {
852 index: 1,
853 type_: ftrippoint::TripPointType::OneshotTempBelow,
854 configuration: ftrippoint::TripPointValue::ClearedTripPoint(
855 ftrippoint::ClearedTripPoint,
856 ),
857 },
858 ]));
859 }
860 ftrippoint::TripPointRequest::SetTripPoints { descriptors, responder } => {
861 let out_of_range = descriptors.iter().any(|d| match &d.configuration {
862 ftrippoint::TripPointValue::OneshotTempAboveTripPoint(tp) => {
863 tp.critical_temperature_celsius <= -273.15
864 }
865 ftrippoint::TripPointValue::OneshotTempBelowTripPoint(tp) => {
866 tp.critical_temperature_celsius <= -273.15
867 }
868 _ => false,
869 });
870 if out_of_range {
871 let _ = responder.send(Err(zx::Status::OUT_OF_RANGE.into_raw()));
872 } else {
873 recorded_batches_clone.lock().unwrap().push(descriptors);
874 let _ = responder.send(Ok(()));
875 }
876 }
877 _ => unreachable!(),
878 }
879 }
880 });
881
882 fasync::unblock(move || {
883 let proxy = ftrippoint::TripPointSynchronousProxy::new(client_end.into_channel());
884 let trip_point = ThermalZoneTripPoint::new("dual-sensor".to_string(), proxy);
885
886 assert_eq!(trip_point.set_hyst_mc(3_000), Ok(()));
889 assert_eq!(trip_point.state.lock().cached_indices, Some((0, Some(1))));
890 assert_eq!(trip_point.set_temp_mc(75_000), Ok(()));
891
892 assert_eq!(trip_point.set_hyst_mc(5_000), Ok(()));
895 assert_eq!(trip_point.hyst_mc(), 5_000);
896
897 assert_eq!(trip_point.set_hyst_mc(400_000), error!(EINVAL));
901 assert_eq!(trip_point.hyst_mc(), 5_000);
902
903 assert_eq!(trip_point.set_temp_mc(0), Ok(()));
905 assert_eq!(trip_point.temp_mc(), 0);
906
907 let batches = recorded_batches.lock().unwrap();
908 assert_eq!(
909 *batches,
910 vec![
911 vec![
912 ftrippoint::TripPointDescriptor {
913 index: 0,
914 type_: ftrippoint::TripPointType::OneshotTempAbove,
915 configuration: ftrippoint::TripPointValue::OneshotTempAboveTripPoint(
916 ftrippoint::OneshotTempAboveTripPoint {
917 critical_temperature_celsius: 75.0,
918 },
919 ),
920 },
921 ftrippoint::TripPointDescriptor {
922 index: 1,
923 type_: ftrippoint::TripPointType::OneshotTempBelow,
924 configuration: ftrippoint::TripPointValue::OneshotTempBelowTripPoint(
925 ftrippoint::OneshotTempBelowTripPoint {
926 critical_temperature_celsius: 72.0,
927 },
928 ),
929 },
930 ],
931 vec![
932 ftrippoint::TripPointDescriptor {
933 index: 0,
934 type_: ftrippoint::TripPointType::OneshotTempAbove,
935 configuration: ftrippoint::TripPointValue::OneshotTempAboveTripPoint(
936 ftrippoint::OneshotTempAboveTripPoint {
937 critical_temperature_celsius: 75.0,
938 },
939 ),
940 },
941 ftrippoint::TripPointDescriptor {
942 index: 1,
943 type_: ftrippoint::TripPointType::OneshotTempBelow,
944 configuration: ftrippoint::TripPointValue::OneshotTempBelowTripPoint(
945 ftrippoint::OneshotTempBelowTripPoint {
946 critical_temperature_celsius: 70.0,
947 },
948 ),
949 },
950 ],
951 vec![
952 ftrippoint::TripPointDescriptor {
953 index: 0,
954 type_: ftrippoint::TripPointType::OneshotTempAbove,
955 configuration: ftrippoint::TripPointValue::ClearedTripPoint(
956 ftrippoint::ClearedTripPoint,
957 ),
958 },
959 ftrippoint::TripPointDescriptor {
960 index: 1,
961 type_: ftrippoint::TripPointType::OneshotTempBelow,
962 configuration: ftrippoint::TripPointValue::ClearedTripPoint(
963 ftrippoint::ClearedTripPoint,
964 ),
965 },
966 ],
967 ]
968 );
969 })
970 .await;
971 }
972}