Skip to content

Conversation

@tullom
Copy link

@tullom tullom commented Nov 24, 2025

Add SMBus support in the form of a helper trait built on top of an embedded-hal-async I2C implementation.

Based on the SMBus v3,3 spec.

@tullom tullom self-assigned this Nov 24, 2025
@tullom tullom requested a review from a team as a code owner November 24, 2025 10:05
@tullom tullom added the enhancement New feature or request label Nov 24, 2025
@github-actions
Copy link

github-actions bot commented Nov 24, 2025

Cargo Vet Audit Passed

cargo vet has passed in this PR. No new unvetted dependencies were found.

@jeffglaum jeffglaum moved this to In review in Embedded Controller Nov 24, 2025
if use_pec {
let pec = Self::get_pec_calc();
if let Some(mut pec) = pec {
pec.write_u8(address << 1);

Choose a reason for hiding this comment

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

Is it possible to rewrite these functions in terms of a generic buffer read/write functions. I was thinking something like write_buffer(address: _ , reg: _, pec: bool, buffer: &[u8]). Then write_byte would be implemented as write_buffer(address, reg, pec, &[byte])`. This would allow implementers to override the core read/write functionality in a single place.

Copy link
Author

Choose a reason for hiding this comment

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

This is already enforced as part of the associated type PecCalc having the core::hash::Hasher trait.

write() is defined in the Hasher trait to take in a bytestream and compute a hash on it, so that functionality is already captured.

Choose a reason for hiding this comment

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

I'm talking about the default implementation for the trait functions. There's duplication of the PEC calculation and duplication of I2C bus operations. I think you could create Smbus::read_buf/Smbus::write_buf functions that take a buffer and do the PEC calculation and the I2C operation. Then functions like Smbus::write_byte can be implemented in terms of Smbus::write_buf.

Copy link
Author

Choose a reason for hiding this comment

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

I noodled around with this for a while and could not find a nice way to calculate the PEC in a generic way. A PEC byte needs to be appended to the end of a write buffer, of which the size of a buffer is only known to the calling function.

Even worse, any i2c::transaction() call cannot append a PEC byte to a write operation in a generic way because Operation::Write()s contain immutable references to the underlying buffer. The slice of Operations is mutable, but a new Operation cannot be added without having some sort of allocated collection. The user could pass in storage for the PEC byte but i think that would make the API arguments confusing.

I tried defining my own Operation, but this breaks interop with i2c::transaction(). I cannot construct the slice of [i2c::Operation] from [smbus::Operation] because I would need to allocate a collection to pass along to i2c::transaction().

TBH, the user is not intended to override any of these methods. So long as the underlying I2C driver conforms to the embedded_hal_async::i2c::I2c trait, there is a guarantee that the default SMBus trait methods will work. I could make this whole trait a wrapper struct on a I2C driver, but I think it more ergonomic to tack a trait to existing I2C drivers.

Copy link

@RobertZ2011 RobertZ2011 Dec 4, 2025

Choose a reason for hiding this comment

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

I could make this whole trait a wrapper struct on a I2C driver, but I think it more ergonomic to tack a trait to existing I2C drivers.

I think a combination of both approaches might be best. We can keep trait Smbus: I2c and the current default implementation becomes the impl<B: I2c> Smbus for SwSmbusI2c<B>. There's still some de-duplication in the code that can be done with an auxiliary function or two which tells me we want the current default logic in its own struct.

@tullom tullom requested a review from RobertZ2011 November 24, 2025 19:32
@tullom tullom requested a review from a team as a code owner November 24, 2025 19:37
@tullom tullom requested review from felipebalbi and kurtjd November 25, 2025 05:09
if use_pec {
let pec = Self::get_pec_calc();
if let Some(mut pec) = pec {
pec.write_u8(address << 1);

Choose a reason for hiding this comment

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

I'm talking about the default implementation for the trait functions. There's duplication of the PEC calculation and duplication of I2C bus operations. I think you could create Smbus::read_buf/Smbus::write_buf functions that take a buffer and do the PEC calculation and the I2C operation. Then functions like Smbus::write_byte can be implemented in terms of Smbus::write_buf.

kurtjd
kurtjd previously approved these changes Nov 25, 2025
Copy link

@kurtjd kurtjd left a comment

Choose a reason for hiding this comment

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

I think Robert's comments are a good idea but otherwise looks good.

crate::smbus::bus::ErrorKind::Pec,
))?;

pec.write_u8(address << 1);
Copy link

@RobertZ2011 RobertZ2011 Dec 4, 2025

Choose a reason for hiding this comment

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

You should be able to use the same i2c::transaction-based logic as write_block so that the caller doesn't have to allocate memory for the PEC byte. This would also help simplify functions like send_byte.

use_pec: bool,
read: &mut [u8],
) -> impl core::future::Future<Output = Result<(), <Self as crate::smbus::bus::ErrorType>::Error>> {
async move {

Choose a reason for hiding this comment

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

Similar to write_buf, can you use transactions here so the caller doesn't have to provide space for the PEC byte?

let mut pec = Self::get_pec_calc().ok_or(<Self as crate::smbus::bus::ErrorType>::Error::to_kind(
crate::smbus::bus::ErrorKind::Pec,
))?;
pec.write_u8(address << 1);

Choose a reason for hiding this comment

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

You should be able to break-out most of the logic in read_byte/read_word into a helper function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cargo vet enhancement New feature or request

Projects

Status: In review

Development

Successfully merging this pull request may close these issues.

3 participants