Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

I2C transaction breaks I2c trait contract #82

Open
KaSroka opened this issue May 18, 2022 · 4 comments
Open

I2C transaction breaks I2c trait contract #82

KaSroka opened this issue May 18, 2022 · 4 comments

Comments

@KaSroka
Copy link

KaSroka commented May 18, 2022

According to I2c trait transaction contract is as follows:

Transaction contract:
- Before executing the first operation an ST is sent automatically. This is followed by SAD+R/W as appropriate.
- Data from adjacent operations of the same type are sent after each other without an SP or SR.
- Between adjacent operations of a different type an SR and SAD+R/W is sent.
- After executing the last operation an SP is sent automatically.
- If the last operation is a `Read` the master does not send an acknowledge for the last byte.
- `ST` = start condition
- `SAD+R/W` = slave address followed by bit 1 to indicate reading or 0 to indicate writing
- `SR` = repeated start condition
- `SP` = stop condition

Using following simple test code:

use embedded_hal::i2c::blocking::{I2c, Operation as I2cOperation};
use linux_embedded_hal::I2cdev;

fn main() {
    let mut i2c_dev = I2cdev::new("/dev/i2c-1").unwrap();

    let data = [0xaa; 1];
    let mut ops = [I2cOperation::Write(&[0x40]), I2cOperation::Write(&data)];
    i2c_dev.transaction(0x3d, &mut ops).unwrap();
}

I'm expecting no SR "repeated start condition" to appear as both operations are of the same type (Write). Unfortunately I can see SR "repeated start condition" on the bus as well as SAD+R/W "slave address followed by bit 1 to indicate reading or 0 to indicate writing" (positions 6 and 8). This was captured using logic analyzer on embedded Linux board for the sample code attached above:
image

@eldruin
Copy link
Member

eldruin commented Aug 4, 2022

Interesting find!
Following the code path you can see that two linux write messages are created and sent to the underlying rust-i2cdev library.
There it is received and the message addresses filled and then sent to the kernel via FFI.
The kernel documentation states:

The function will write or read data to or from that buffers depending on whether the I2C_M_RD flag is set in a particular message or not. The slave address and whether to use ten bit address mode has to be set in each message, overriding the values set with the above ioctl’s.

So the code path seems to be correct and this is just how the linux kernel works: it sends the messages without a stop in between, although it does send a restart with the same address for each message regardless of the type of the last one.

In order to uphold the transaction contract what we could do is to consolidate operations of the same type that follow each other into a single message for the kernel here.
This needs to be done for transaction_iter as well, but the _iter part of it is internally already collected and not immediately sent so it should be doable.

This affects the 0.3.x version as well.

@SZenglein
Copy link

So according to https://docs.kernel.org/i2c/i2c-protocol.html, there exists a flag I2C_M_NOSTART that can be set for each message.

The description states:

This is often used to gather transmits from multiple data buffers in system memory into something that appears as a single transfer to the I2C device but may also be used between direction changes by some rare devices.

I think this might do exactly what the I2c trait contract requires.

@ohunter
Copy link

ohunter commented May 3, 2024

The I2C_M_NOSTART flag does fix the issue, but it assumes that the i2c device supports it. This can be checked using the I2C_FUNCS operation of ioctl. One potential issue with the fix is that it needs to be implemented in the rust-i2cdev repo as the i2c_mgs.flags field is private. If we can assume that the LinuxI2CDevice::transfer call should always fulfill the contract then this isn't an issue at all.

@eldruin
Copy link
Member

eldruin commented May 3, 2024

I am not opposed to make rust-i2cdev fulfill the contract of embedded-hal::i2c::I2c. However, it is possible to set the NO_START message flag entirely from this crate by using with_flags(). i.e. LinuxI2CMessage::write(w).with_flags(I2CMessageFlags::NO_START).
One would need to play a bit with the chain here, though, in order to set it only when appropriate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants