Validate 7-bit I2C addresses (#3343)

* Validate 7-bit I2C addresses

* add test

* CHANGELOG.md

* Docs

* Include address in `Error::AddressInvalid`

* Fix

* CHANGELOG.md

* Revert CHANGELOG change

---------

Co-authored-by: Dániel Buga <bugadani@gmail.com>
This commit is contained in:
Björn Quentin 2025-04-08 13:35:36 +02:00 committed by GitHub
parent c22797c694
commit 523bd381eb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 51 additions and 3 deletions

View File

@ -50,6 +50,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fix PCNT counter not keeping the peripheral enabled (#3334) - Fix PCNT counter not keeping the peripheral enabled (#3334)
- Fixed an issue where inverting a pin via the interconnect matrix was ineffective (#3312) - Fixed an issue where inverting a pin via the interconnect matrix was ineffective (#3312)
- The half-duplex SPI APIs should accept more valid line width combinations (#3325) - The half-duplex SPI APIs should accept more valid line width combinations (#3325)
- Passing an invalid seven bit I2C address is now rejected (#3343)
- PARL_IO: Use correct max transfer size (#3346) - PARL_IO: Use correct max transfer size (#3346)
### Removed ### Removed

View File

@ -79,17 +79,33 @@ const MAX_ITERATIONS: u32 = 1_000_000;
pub enum I2cAddress { pub enum I2cAddress {
/// 7-bit address mode type. /// 7-bit address mode type.
/// ///
/// Note that 7-bit addresses defined by drivers should be specified in /// Note that 7-bit addresses are specified in **right-aligned** form, e.g.
/// **right-aligned** form, e.g. in the range `0x00..=0x7F`. /// in the range `0x00..=0x7F`.
/// ///
/// For example, a device that has the seven bit address of `0b011_0010`, /// For example, a device that has the seven bit address of `0b011_0010`,
/// and therefore is addressed on the wire using: /// and therefore is addressed on the wire using:
/// ///
/// * `0b0110010_0` or `0x64` for *writes* /// * `0b0110010_0` or `0x64` for *writes*
/// * `0b0110010_1` or `0x65` for *reads* /// * `0b0110010_1` or `0x65` for *reads*
///
/// The above address is specified as 0b0011_0010 or 0x32, NOT 0x64 or 0x65.
SevenBit(u8), SevenBit(u8),
} }
impl I2cAddress {
fn validate(&self) -> Result<(), Error> {
match self {
I2cAddress::SevenBit(addr) => {
if *addr > 0x7F {
return Err(Error::AddressInvalid(*self));
}
}
}
Ok(())
}
}
impl From<u8> for I2cAddress { impl From<u8> for I2cAddress {
fn from(value: u8) -> Self { fn from(value: u8) -> Self {
I2cAddress::SevenBit(value) I2cAddress::SevenBit(value)
@ -168,6 +184,8 @@ pub enum Error {
CommandNumberExceeded, CommandNumberExceeded,
/// Zero length read or write operation. /// Zero length read or write operation.
ZeroLengthInvalid, ZeroLengthInvalid,
/// The given address is invalid.
AddressInvalid(I2cAddress),
} }
/// I2C no acknowledge error reason. /// I2C no acknowledge error reason.
@ -231,6 +249,9 @@ impl core::fmt::Display for Error {
write!(f, "The number of commands issued exceeded the limit") write!(f, "The number of commands issued exceeded the limit")
} }
Error::ZeroLengthInvalid => write!(f, "Zero length read or write operation"), Error::ZeroLengthInvalid => write!(f, "Zero length read or write operation"),
Error::AddressInvalid(address) => {
write!(f, "The given address ({:?}) is invalid", address)
}
} }
} }
} }
@ -794,6 +815,8 @@ impl<'d> I2c<'d, Async> {
address: I2cAddress, address: I2cAddress,
operations: impl Iterator<Item = Operation<'a>>, operations: impl Iterator<Item = Operation<'a>>,
) -> Result<(), Error> { ) -> Result<(), Error> {
address.validate()?;
let mut last_op: Option<OpKind> = None; let mut last_op: Option<OpKind> = None;
// filter out 0 length read operations // filter out 0 length read operations
let mut op_iter = operations let mut op_iter = operations
@ -866,6 +889,8 @@ where
address: I2cAddress, address: I2cAddress,
operations: impl Iterator<Item = Operation<'a>>, operations: impl Iterator<Item = Operation<'a>>,
) -> Result<(), Error> { ) -> Result<(), Error> {
address.validate()?;
let mut last_op: Option<OpKind> = None; let mut last_op: Option<OpKind> = None;
// filter out 0 length read operations // filter out 0 length read operations
let mut op_iter = operations let mut op_iter = operations
@ -2211,6 +2236,7 @@ impl Driver<'_> {
bytes: &[u8], bytes: &[u8],
start: bool, start: bool,
) -> Result<(), Error> { ) -> Result<(), Error> {
address.validate()?;
self.reset_fifo(); self.reset_fifo();
self.reset_command_list(); self.reset_command_list();
let cmd_iterator = &mut self.regs().comd_iter(); let cmd_iterator = &mut self.regs().comd_iter();
@ -2244,6 +2270,7 @@ impl Driver<'_> {
start: bool, start: bool,
will_continue: bool, will_continue: bool,
) -> Result<(), Error> { ) -> Result<(), Error> {
address.validate()?;
self.reset_fifo(); self.reset_fifo();
self.reset_command_list(); self.reset_command_list();
@ -2275,6 +2302,7 @@ impl Driver<'_> {
start: bool, start: bool,
stop: bool, stop: bool,
) -> Result<(), Error> { ) -> Result<(), Error> {
address.validate()?;
self.clear_all_interrupts(); self.clear_all_interrupts();
// Short circuit for zero length writes without start or end as that would be an // Short circuit for zero length writes without start or end as that would be an
@ -2311,6 +2339,7 @@ impl Driver<'_> {
stop: bool, stop: bool,
will_continue: bool, will_continue: bool,
) -> Result<(), Error> { ) -> Result<(), Error> {
address.validate()?;
self.clear_all_interrupts(); self.clear_all_interrupts();
// Short circuit for zero length reads as that would be an invalid operation // Short circuit for zero length reads as that would be an invalid operation
@ -2364,6 +2393,7 @@ impl Driver<'_> {
start: bool, start: bool,
stop: bool, stop: bool,
) -> Result<(), Error> { ) -> Result<(), Error> {
address.validate()?;
self.clear_all_interrupts(); self.clear_all_interrupts();
// Short circuit for zero length writes without start or end as that would be an // Short circuit for zero length writes without start or end as that would be an
@ -2400,6 +2430,7 @@ impl Driver<'_> {
stop: bool, stop: bool,
will_continue: bool, will_continue: bool,
) -> Result<(), Error> { ) -> Result<(), Error> {
address.validate()?;
self.clear_all_interrupts(); self.clear_all_interrupts();
// Short circuit for zero length reads as that would be an invalid operation // Short circuit for zero length reads as that would be an invalid operation

View File

@ -7,7 +7,7 @@
#![no_main] #![no_main]
use esp_hal::{ use esp_hal::{
i2c::master::{AcknowledgeCheckFailedReason, Config, Error, I2c, Operation}, i2c::master::{AcknowledgeCheckFailedReason, Config, Error, I2c, I2cAddress, Operation},
Async, Async,
Blocking, Blocking,
}; };
@ -49,6 +49,22 @@ mod tests {
Context { i2c } Context { i2c }
} }
#[test]
fn invalid_address_returns_error(mut ctx: Context) {
assert_eq!(
ctx.i2c.write(0x80, &[]),
Err(Error::AddressInvalid(I2cAddress::SevenBit(0x80)))
);
assert_eq!(
ctx.i2c.read(0x80, &mut [0; 1]),
Err(Error::AddressInvalid(I2cAddress::SevenBit(0x80)))
);
assert_eq!(
ctx.i2c.write_read(0x80, &[0x77], &mut [0; 1]),
Err(Error::AddressInvalid(I2cAddress::SevenBit(0x80)))
);
}
#[test] #[test]
fn empty_write_returns_ack_error_for_unknown_address(mut ctx: Context) { fn empty_write_returns_ack_error_for_unknown_address(mut ctx: Context) {
// on some chips we can determine the ack-check-failed reason but not on all // on some chips we can determine the ack-check-failed reason but not on all