Add SMBus HAL, dependent on embedded-hal-async::I2c#7
Conversation
Cargo Vet Audit Failed
If the unvetted dependencies are not neededPlease modify Cargo.toml file to avoid including the dependencies. If the unvetted dependencies are neededPost a new comment with the questionnaire below to the PR to help the auditors vet the dependencies. Copy and paste the questionnaire as a new comment and provide your answers:1. What crates (with version) need to be audited? 2. How many of the crates are version updates vs new dependencies? 3. To confirm none of the already included crates serve your needs, please provide a brief description of the purpose of the new crates. 4. Any extra notes to the auditors to help with their audits. |
kurtjd
left a comment
There was a problem hiding this comment.
I think Robert's comments are a good idea but otherwise looks good.
09cc4af to
28fff71
Compare
|
@tullom This looks fine to me as long as some of Robert's comments are addressed. I would like to see a demonstration of this on one of the charger/fuel gage driver. Put it in action is good way to iron out the kinks. Non-blocking: Another consideration would be to use |
There was a problem hiding this comment.
Pull request overview
Adds an SMBus helper API to embedded-mcu-hal, implemented as an async trait layered on top of embedded-hal-async::i2c::I2c, along with the dependency and cargo-vet metadata updates needed to consume embedded-hal/embedded-hal-async.
Changes:
- Introduces a new
smbusmodule with shared SMBus error types and an async SMBus helper trait (Smbus) built onembedded-hal-asyncI2C operations. - Adds
embedded-hal+embedded-hal-asyncdependencies and wiresdefmtfeature integration forembedded-hal. - Updates
Cargo.lockand cargo-vet audit/import lockfiles for the new crates.
Reviewed changes
Copilot reviewed 6 out of 8 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| supply-chain/imports.lock | Adds/imports cargo-vet entries for embedded-hal and embedded-hal-async; includes minor note formatting changes. |
| supply-chain/audits.toml | Adds a cargo-vet audit entry for defmt 0.3.x. |
| embedded-mcu-hal/src/lib.rs | Exposes the new smbus module from the crate root. |
| embedded-mcu-hal/Cargo.toml | Adds embedded-hal/embedded-hal-async deps and extends the defmt feature to enable embedded-hal defmt support. |
| Cargo.lock | Locks new dependencies (embedded-hal, embedded-hal-async, and defmt 0.3.x). |
| embedded-mcu-hal/src/smbus/mod.rs | Adds the smbus module root. |
| embedded-mcu-hal/src/smbus/bus/mod.rs | Defines SMBus error traits/types (Error, ErrorKind, ErrorType). |
| embedded-mcu-hal/src/smbus/bus/asynch.rs | Implements the async SMBus helper trait with PEC support and SMBus read/write/process/block operations. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 8 changed files in this pull request and generated 9 comments.
Comments suppressed due to low confidence (4)
embedded-mcu-hal/src/smbus/bus/asynch.rs:354
finalize_pec_byteusestry_into()which fails if the hasher'sfinish()value doesn't fit intou8. Earlier docs state that the PEC is the truncated low byte offinish(), so this should explicitly truncate/mask instead of erroring on largeru64values.
/// Truncate a finished PEC value to its low byte.
fn finalize_pec_byte(pec: u64) -> Result<u8, crate::smbus::bus::ErrorKind> {
pec.try_into().map_err(|_| crate::smbus::bus::ErrorKind::Pec)
}
embedded-mcu-hal/src/smbus/bus/asynch.rs:629
block_writeis implemented as a transaction with multipleOperation::Writesteps (register, length, payload, PEC). This will generally insert repeated STARTs/address bytes between chunks, which is not equivalent to an SMBus block write frame and will also break PEC calculation. Build a single contiguous write buffer for the whole write phase (register + len + data [+ pec]) and send it as one write op.
self.i2c
.transaction(
address,
&mut [
Operation::Write(&[register]),
Operation::Write(&[data.len() as u8]),
Operation::Write(data),
Operation::Write(&[pec]),
],
)
embedded-mcu-hal/src/smbus/bus/asynch.rs:725
block_write_block_read_process_callis executed as many separate write/read operations inside a singletransaction. This will generally introduce extra repeated STARTs/address bytes between operations, changing the SMBus wire sequence and PEC inputs (similar toprocess_callandblock_read). Restructure to a single contiguous write op for the write phase and a single contiguous read op for the read phase (then parse length/data/pec).
self.i2c
.transaction(
address,
&mut [
Operation::Write(&[register]),
Operation::Write(&[write_data.len() as u8]),
Operation::Write(write_data),
Operation::Read(&mut read_msg_size),
Operation::Read(read_data),
Operation::Read(&mut pec_buf),
],
)
embedded-mcu-hal/src/smbus/bus/mod.rs:160
- The
ErrorTypetrait is documented as "I2C error type trait" even though it is defining the error type for the SMBus traits in this module. Updating the wording will avoid confusion for HAL/driver authors implementing this API.
/// I2C error type trait.
///
/// This just defines the error type, to be used by the other traits.
pub trait ErrorType {
/// Error type
type Error: Error + From<embedded_hal_async::i2c::ErrorKind>;
}
| //! Async SMBus controller trait and software implementation. | ||
| //! | ||
| //! This module defines the [`Smbus`] async controller trait describing the | ||
| //! SMBus protocol surface. The trait declares the protocol-level | ||
| //! operations as required methods plus two associated items — | ||
| //! [`Smbus::PecCalc`] and [`Smbus::get_pec_calc`] — and a |
| use crate::smbus::bus::Error as SMBusError; | ||
| use embedded_hal_async::i2c::{Error as I2cError, I2c, Operation}; |
| computed_pec | ||
| .eq(&received_pec.into()) |
| if use_pec { | ||
| let mut pec = Self::pec_calc_with_write_addr(address)?; | ||
| self.i2c | ||
| .read(address, read) | ||
| .await | ||
| .map_err(|e| crate::smbus::bus::ErrorKind::from(e.kind()))?; | ||
| let (pec_byte, rest) = read.split_last().ok_or(crate::smbus::bus::ErrorKind::Pec)?; | ||
| pec.write(rest); | ||
| <Self as Smbus>::check_pec(*pec_byte, pec.finish())?; | ||
| } else { |
| let mut buf = [0u8; 3]; | ||
| let mut pec = Self::pec_calc_with_write_addr(address)?; | ||
| pec.write_u8(register); | ||
| pec.write_u16(word); |
| let mut msg_size = [0u8]; | ||
| if use_pec { | ||
| let mut pec_buf = [0u8]; | ||
| let mut pec = Self::pec_calc_with_write_addr(address)?; | ||
| pec.write_u8(register); | ||
| pec.write_u8(crate::smbus::bus::read_address_byte(address)); | ||
| self.i2c | ||
| .transaction( | ||
| address, | ||
| &mut [ | ||
| Operation::Write(&[register]), | ||
| Operation::Read(&mut msg_size), | ||
| Operation::Read(data), | ||
| Operation::Read(&mut pec_buf), | ||
| ], | ||
| ) | ||
| .await | ||
| .map_err(|e| crate::smbus::bus::ErrorKind::from(e.kind()))?; | ||
| pec.write(&msg_size); | ||
| pec.write(data); | ||
| Self::check_pec(pec_buf[0], pec.finish())?; | ||
| } else { | ||
| self.i2c | ||
| .transaction( | ||
| address, | ||
| &mut [ | ||
| Operation::Write(&[register]), | ||
| Operation::Read(&mut msg_size), | ||
| Operation::Read(data), | ||
| ], | ||
| ) | ||
| .await | ||
| .map_err(|e| crate::smbus::bus::ErrorKind::from(e.kind()))?; | ||
| } | ||
| Ok(()) |
Split the async SMBus surface into an abstract protocol trait and a concrete software implementation. The `Smbus` trait now declares only the SMBus protocol operations plus the PEC associated items (`PecCalc`, `get_pec_calc`, `check_pec`). All default implementations that bit-bang the protocol on top of an I²C bus have moved into a new `SwSmbusI2c<I, P>` wrapper struct that owns an `embedded_hal_async::i2c::I2c` bus and delegates PEC calculator construction to a new `PecProvider` trait via the `P` parameter. HALs with a hardware SMBus peripheral can implement `Smbus` directly without inheriting any I²C-specific machinery. `write_buf`, `read_buf`, and `write_read_buf` are no longer trait methods; they are inherent helpers on `SwSmbusI2c` that the other protocol methods reuse, eliminating duplicated PEC setup and I²C error mapping across the byte/word/block operations. PEC calculator priming with the write-address byte and the truncation of the finished hash to a single byte are likewise factored into private helpers (`pec_calc_with_write_addr`, `finalize_pec_byte`). The blanket `impl<T: Smbus + ?Sized> Smbus for &mut T` forwarding impl is preserved, and `SwSmbusI2c::Error` is `ErrorKind` directly so callers do not need to define a wrapper error type. Tests are reworked to drive `SwSmbusI2c<I2cMock, _>` through small `PecProvider` impls (`TestPec`, `NoPec`). Assisted-by: GitHub Copilot:claude-opus-4.7
| /// } | ||
| /// } | ||
| /// | ||
| /// fn to_kind(kind: crate::smbus::bus::ErrorKind) -> Self { |
There was a problem hiding this comment.
Looks more like a from_kind
| &mut self, | ||
| address: u8, | ||
| byte: u8, | ||
| use_pec: bool, |
There was a problem hiding this comment.
How about making the API explicit? That is, send_byte() and send_byte_with_pec()
RobertZ2011
left a comment
There was a problem hiding this comment.
Nothing to add on top of the existing comments.
kurtjd
left a comment
There was a problem hiding this comment.
Looks good (though Copilot suggestions seem like they might be valid).
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.