Skip to content

Add SMBus HAL, dependent on embedded-hal-async::I2c#7

Open
tullom wants to merge 3 commits into
OpenDevicePartnership:mainfrom
tullom:smbus-hal
Open

Add SMBus HAL, dependent on embedded-hal-async::I2c#7
tullom wants to merge 3 commits into
OpenDevicePartnership:mainfrom
tullom:smbus-hal

Conversation

@tullom
Copy link
Copy Markdown

@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
Copy Markdown

github-actions Bot commented Nov 24, 2025

Cargo Vet Audit Failed

cargo vet has failed in this PR. Please run cargo vet --locked locally to check for new or updated unvetted dependencies.
Details about the vetting process can be found in supply-chain/README.md

If the unvetted dependencies are not needed

Please modify Cargo.toml file to avoid including the dependencies.

If the unvetted dependencies are needed

Post a new comment with the questionnaire below to the PR to help the auditors vet the dependencies.
After the auditors have vetted the dependencies, the PR will need to be rebased to pick up the new audits and pass this check.

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.

@jeffglaum jeffglaum moved this to In review in ODP Backlog Nov 24, 2025
Comment thread embedded-mcu-hal/src/smbus/bus/asynch.rs Outdated
@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
Comment thread embedded-mcu-hal/src/smbus/bus/asynch.rs Outdated
kurtjd
kurtjd previously approved these changes Nov 25, 2025
Copy link
Copy Markdown

@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.

Comment thread embedded-mcu-hal/src/smbus/bus/asynch.rs Outdated
Comment thread embedded-mcu-hal/src/smbus/bus/asynch.rs Outdated
Comment thread embedded-mcu-hal/src/smbus/bus/asynch.rs Outdated
@jerrysxie
Copy link
Copy Markdown
Contributor

@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 embedded-hal i2c mock to unit test some of the default implementation. Please create an issue for this.

@tullom tullom moved this from In review to Backlog in ODP Backlog Apr 7, 2026
Copilot AI review requested due to automatic review settings May 12, 2026 18:01
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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 smbus module with shared SMBus error types and an async SMBus helper trait (Smbus) built on embedded-hal-async I2C operations.
  • Adds embedded-hal + embedded-hal-async dependencies and wires defmt feature integration for embedded-hal.
  • Updates Cargo.lock and 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.

Comment thread embedded-mcu-hal/src/smbus/bus/asynch.rs Outdated
Comment thread embedded-mcu-hal/src/smbus/bus/asynch.rs
Comment thread embedded-mcu-hal/src/smbus/bus/asynch.rs Outdated
Comment thread embedded-mcu-hal/src/smbus/bus/asynch.rs Outdated
Comment thread embedded-mcu-hal/src/smbus/bus/asynch.rs Outdated
Comment thread embedded-mcu-hal/src/smbus/bus/asynch.rs Outdated
Comment thread embedded-mcu-hal/src/smbus/bus/asynch.rs Outdated
Comment thread embedded-mcu-hal/src/smbus/bus/mod.rs
Comment thread embedded-mcu-hal/src/smbus/bus/asynch.rs
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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_byte uses try_into() which fails if the hasher's finish() value doesn't fit into u8. Earlier docs state that the PEC is the truncated low byte of finish(), so this should explicitly truncate/mask instead of erroring on larger u64 values.
    /// 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_write is implemented as a transaction with multiple Operation::Write steps (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_call is executed as many separate write/read operations inside a single transaction. This will generally introduce extra repeated STARTs/address bytes between operations, changing the SMBus wire sequence and PEC inputs (similar to process_call and block_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 ErrorType trait 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>;
}

Comment on lines +1 to +6
//! 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
Comment on lines +20 to +21
use crate::smbus::bus::Error as SMBusError;
use embedded_hal_async::i2c::{Error as I2cError, I2c, Operation};
Comment on lines +74 to +75
computed_pec
.eq(&received_pec.into())
Comment on lines +396 to +405
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);
Comment thread embedded-mcu-hal/src/smbus/bus/asynch.rs
Comment thread embedded-mcu-hal/src/smbus/bus/asynch.rs
Comment on lines +658 to +692
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(())
Comment thread embedded-mcu-hal/src/smbus/bus/mod.rs
@tullom tullom requested a review from RobertZ2011 May 13, 2026 01:26
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 {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks more like a from_kind

&mut self,
address: u8,
byte: u8,
use_pec: bool,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

How about making the API explicit? That is, send_byte() and send_byte_with_pec()

Copy link
Copy Markdown

@RobertZ2011 RobertZ2011 left a comment

Choose a reason for hiding this comment

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

Nothing to add on top of the existing comments.

Copy link
Copy Markdown

@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.

Looks good (though Copilot suggestions seem like they might be valid).

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: Backlog

Development

Successfully merging this pull request may close these issues.

7 participants