Skip to content
Open
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
3123a58
Try to push down some state into the BSP itself
jamesmunns Jul 22, 2026
ded5468
Also misc_sensors
jamesmunns Jul 22, 2026
c1919e6
WIP: start working on input
jamesmunns Jul 23, 2026
45e7f1e
WIP: Extract state to BSP
jamesmunns Jul 23, 2026
e3fb65d
Bring us back down to even
jamesmunns Jul 23, 2026
a87ed16
Start decomposing run_control
jamesmunns Jul 23, 2026
823a7dc
Push logic into InputChannel
jamesmunns Jul 23, 2026
4d2fdbd
Hoist logic back into control
jamesmunns Jul 24, 2026
29418d2
Split out InputMetadata to reduce static RAM usage
jamesmunns Jul 24, 2026
dc7a597
More putzing
jamesmunns Jul 24, 2026
49f959d
I think it was wrong to split up fan presence and rpm reading.
jamesmunns Jul 24, 2026
952d1bf
Split presence/read back up, finish cleaning up fans
jamesmunns Jul 24, 2026
d959df3
It's a trait!
jamesmunns Jul 24, 2026
a05d9fb
Port over gimlet to new style impl
jamesmunns Jul 24, 2026
36f5ac8
Address review comments
jamesmunns Jul 27, 2026
befe410
Push down I2C logic into bsp-specific code
jamesmunns Jul 27, 2026
e1e8bef
Port grapefruit
jamesmunns Jul 27, 2026
9abcdfe
Medusa does not have a thermal task
jamesmunns Jul 27, 2026
eac9093
Port over minibar
jamesmunns Jul 27, 2026
b2de0ab
WIP sidecar, but I think I need to check something
jamesmunns Jul 27, 2026
8102d44
Clarify state tracking of InputChannel
jamesmunns Jul 27, 2026
96e716c
Implement the rest of sidecar
jamesmunns Jul 27, 2026
908f0c6
Helper function for const init
jamesmunns Jul 27, 2026
2658936
Fix grapefruit
jamesmunns Jul 27, 2026
c1c18f4
More dead code annotations
jamesmunns Jul 27, 2026
d765077
More dead code
jamesmunns Jul 27, 2026
e06c5b0
Track fan presence instead of a pseudo-status
jamesmunns Jul 27, 2026
35ccf1a
Make Input states *really* explicit.
jamesmunns Jul 27, 2026
9df220f
Address a couple small review comments
jamesmunns Jul 27, 2026
e9891b5
Merge branch 'master' into james/thermal-noodling
jamesmunns Jul 28, 2026
5c803a7
Make USE_CONTROLLER a trait const, add docs
jamesmunns Jul 28, 2026
a297744
Address some review comments and correct some stale comments
jamesmunns Jul 29, 2026
2547038
One more
jamesmunns Jul 29, 2026
6b78b8a
Update docs
jamesmunns Jul 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions drv/i2c-devices/src/emc2305.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,27 @@ pub const MAX_FANS: u8 = 5;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Fan(u8);

impl Fan {
/// Create a new fan
///
/// This panics if idx is out of range, and is intended to be used
/// at compile time for building constant values. Prefer `TryFrom`
/// at runtime when handling user provided data.
pub const fn new_const(idx: u8) -> Self {
if idx >= MAX_FANS {
panic!("Out of range!");
Comment on lines +158 to +159

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love the extra potential runtime panic but it also didn't seem to increase the code size so 🤷

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a const-fn, and is only currently used to generate constants, which means that any panics that occur would be compile-time errors.

You are right, if someone inadvertently calls it at runtime, it would potentially panic in that case.

} else {
Self(idx)
}
}
}

impl From<Fan> for u8 {
fn from(val: Fan) -> Self {
val.0
}
}

impl TryFrom<u8> for Fan {
type Error = ();
/// Fans are based on a 0-based index. This should *not* be the number
Expand Down
21 changes: 21 additions & 0 deletions drv/i2c-devices/src/max31790.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,27 @@ pub const MAX_FANS: u8 = 6;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Fan(u8);

impl Fan {
/// Create a new fan
///
/// This panics if idx is out of range, and is intended to be used
/// at compile time for building constant values. Prefer `TryFrom`
/// at runtime when handling user provided data.
pub const fn new_const(idx: u8) -> Self {
if idx >= MAX_FANS {
panic!("Out of range!");
} else {
Self(idx)
}
}
}

impl From<Fan> for u8 {
fn from(val: Fan) -> Self {
val.0
}
}

impl TryFrom<u8> for Fan {
type Error = ();
/// Fans are based on a 0-based index. This should *not* be the number
Expand Down
151 changes: 151 additions & 0 deletions task/thermal/src/bsp/common/emc2305.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

//! Common types and helpers for Emc2305 Fan Controller

use drv_i2c_api::{I2cDevice, ResponseCode};
use drv_i2c_devices::emc2305::Emc2305;
use ringbuf::ringbuf_entry;
use task_sensor_api::SensorId;
use task_thermal_api::{SensorReadError, ThermalError};

use crate::{
__RINGBUF, Trace,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, the ringbuf crate also has a ringbuf_entry_root! macro which is intended to allow submodules to add entries to ringbufs declared in the crate root without having to import __RINGBUF. Not a big deal, as what you're doing also works fine in this case (as there is no additional ringbuf declared in this module, which ringbuf_entry_root! helps with). I just figured I'd mention it.

I've also seen code use ringbuf::ringbuf_entry_root as ringbuf_entry; in cases where a module doesn't have its own ringbuf. I dunno whether or not I love that pattern, but it's an existing convention... 🤷‍♀️

control::{Fan, FanStatus},
};

/// Tracks whether a Emc2305 fan controller has been initialized, and
/// initializes it on demand when accessed, if necessary.
///
/// This is copy-pasted from [`Max31790`]
Comment thread
jamesmunns marked this conversation as resolved.
Outdated
pub(crate) struct Emc2305State {
emc2305: Emc2305,
fan_count: u8,
initialized: bool,
}

impl Emc2305State {
#[allow(dead_code)]
pub(crate) fn new(dev: &I2cDevice, fan_count: u8) -> Self {
let mut this = Self {
emc2305: Emc2305::new(dev),
fan_count,
initialized: false,
};
retry_init(|| this.initialize().map(|_| ()));
this
}

/// Access the fan controller, attempting to initialize it if it has not yet
/// been initialized.
#[inline]
#[allow(dead_code)]
pub(crate) fn try_initialize(
&mut self,
) -> Result<&mut Emc2305, ControllerInitError> {
if self.initialized {
return Ok(&mut self.emc2305);
}

self.initialize()
}

// Slow path that actually performs initialization. This is "outlined" so
// that we can avoid pushing a stack frame in the case where we just need to
// check a bool and return a pointer.
#[inline(never)]
fn initialize(&mut self) -> Result<&mut Emc2305, ControllerInitError> {
self.emc2305.initialize(self.fan_count).map_err(|e| {
ringbuf_entry!(Trace::FanControllerInitError(e));
ControllerInitError(e)
})?;

self.initialized = true;
ringbuf_entry!(Trace::FanControllerInitialized);
Ok(&mut self.emc2305)
}

pub(crate) fn read_fan_rpms(
&mut self,
fans: &mut [Fan<drv_i2c_devices::emc2305::Fan>],
) -> impl Iterator<Item = FanStatus> {
// Try to initialize the fan controller once at the start of the loop
let mut fctrl = self.try_initialize().map_err(SensorReadError::from);

// TODO: Maybe there's a way to make this a method on Fan that we can
// call, kind of like InputStatus?
fans.iter_mut().map(move |f| {
let sensor_id = f.rpm_sensor_id;
if !f.is_present {
return FanStatus::NotPresent { sensor_id };
}

// If initialization failed, then we short circuit to return that
// original error, copied for each fan we're going to report.
let fctrl = fctrl.as_mut().map_err(|e| *e);

// If it was a success, attempt to read the RPMs, and either report
// that success or that error for each fan rpm.
let res = fctrl.and_then(|fc| {
fc.fan_rpm(f.bsp_data).map_err(SensorReadError::I2cError)
});
match res {
Ok(rpm) => FanStatus::PresentSuccess { rpm, sensor_id },
Err(error) => FanStatus::PresentError { error, sensor_id },
}
})
}
}

/// Helper function to retry initialization several times, logging errors
pub(crate) fn retry_init<F: FnMut() -> Result<(), ControllerInitError>>(
mut init: F,
) {
// When we first start up, try to initialize the fan controller a few
// times, in case there's a transient I2C error.
Comment on lines +103 to +104

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this still the case? I see this was originally added for grapefruit as part of 725bdfdd8b01de8e095199ee3f0ee218ffe71414

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure! I haven't actually tested this on hardware yet, and tried as best as possible to preserve the existing behavior, whatever it was.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also worth noting, thermal is only active for grapefruit-ruby, and I'm not sure where to even obtain one of those for testing.

for remaining in (0..3).rev() {
if init().is_ok() {
break;
}
ringbuf_entry!(Trace::FanControllerInitRetry { remaining });
}
}

pub(crate) struct ControllerInitError(pub(crate) ResponseCode);

impl From<ControllerInitError> for ThermalError {
fn from(_: ControllerInitError) -> Self {
ThermalError::FanControllerUninitialized
}
}

impl From<ControllerInitError> for SensorReadError {
fn from(ControllerInitError(code): ControllerInitError) -> Self {
SensorReadError::I2cError(code)
}
}

#[allow(dead_code)]
pub(crate) const fn make_consecutive_nonremovable_fans<const N: usize>(
sensors: &'static [SensorId; N],
) -> [crate::control::Fan<drv_i2c_devices::emc2305::Fan>; N] {
const ONE: crate::control::Fan<drv_i2c_devices::emc2305::Fan> =
crate::control::Fan::new(
SensorId::new(0),
drv_i2c_devices::emc2305::Fan::new_const(0),
);

let mut out = [ONE; N];
let mut idx = 0;
while idx < N {
out[idx] = crate::control::Fan::new(
sensors[idx],
drv_i2c_devices::emc2305::Fan::new_const(idx as u8),
);
out[idx].is_present = true;
idx += 1;
}

out
}
Loading
Loading