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_iter should take an iterator of mutable references #367

Closed
yodaldevoid opened this issue Feb 18, 2022 · 5 comments
Closed
Milestone

Comments

@yodaldevoid
Copy link
Contributor

I was recently porting an I2C controller device to 1.0.0-alpha.7 which forced me to implement transaction and transaction_iter for the first time. I initially tried to implement transaction as follows:

fn transaction<'a>(
    &mut self,
    address: u8,
    operations: &mut [Operation<'a>],
) -> Result<(), Self::Error> {
    self.transaction_iter(address, operations)
}

This, of course, did not work as &mut [Operation<'a>] produces an iterator with Item = &mut Operation<'a> rather than the Item = Operation<'a> that transaction_iter requires.

fn transaction_iter<'a, O>(&mut self, address: u8, operations: O) -> Result<(), Self::Error>
where
    O: IntoIterator<Item = Operation<'a>>,

In my opinion, transaction_iter's signature should be this instead:

fn transaction_iter<'a, 'b: 'a, O>(&mut self, address: u8, operations: O) -> Result<(), Self::Error>
where
    O: IntoIterator<Item = &'a Operation<'b>>,

This signature mainly allows the implementation of transaction that I wrote above. By changing this signature almost all functions of I2C can be implemented as calls to transaction_iter even on systems without alloc support, with write_iter and write_iter_read being the exceptions. This would allow de-duplication of logic, which is rarely a bad thing. Additionally, I see no reason that transaction_iter needs an iterator over owned Operations in the first place.

Thoughts?

@Dirbaio
Copy link
Member

Dirbaio commented Feb 18, 2022

If the iterator yields owned Operations, it can generate them on the fly and return them. If it yields references, it has to allocate all the operations upfront and keep them alive throughout the entire transaction_iter call, as all the yielded references must live for 'a.

However, even with the current "yields owned Operations", the same problem applies to the buffers, they must still be all allocated upfront. Given this I'm not sure transaction_iter has any benefits at all over transaction in the first place... Maybe we should remove it completely.

Also, the whole problem disappears if we change i2c to use a transaction closure like in #351...

@yodaldevoid
Copy link
Contributor Author

You make a good point about needing a way of yielding generated operations. You also make a good point that transaction_iter doesn't really meet this need at the moment.

I'll look at putting together a PR adding something like a what is seen in #351.

@eldruin eldruin added this to the v1.0.0 milestone Aug 9, 2022
@yodaldevoid
Copy link
Contributor Author

As can be expected, while I was far too busy to actually work on this other's have worked at a solution. I think something like #392 is the right path forward.

@tgross35
Copy link

It would be nice to have a default implementation for some of these trait items. If write and read could have default implementations calling write_iter and read_iter, it would nicely reduce the overhead of what somebody needs to write.

Also, what is the reasoning behind keeping the transaction methods within the main I2c trait, rather than something like I2cTransactional? It seems to me like the way it currently is for v2 makes it tougher to write a minimal i2c implementation (without using unimplemented!).

bors bot added a commit that referenced this issue Mar 28, 2023
440: I2c: simplify, expand docs, document shared bus usage. r=eldruin a=Dirbaio

~Depends on #441 -- check that one out first.~

This does some simplifications to the trait that I think we should do:

- Implement all methods in terms of `transaction`. This way HALs have to implement just that.
- Removed byte-wise-iteration methods: `write_iter` and `write_iter_read`. The reason is that they're quite inefficient, especially with DMA implementations. We've already removed these on other traits, so I think we should do as well here.
- Removed `transaction_iter`. I don't think it's much useful in practice, because the way iterators work all the yielded `Operation`s must have the same lifetime. This means that, even if the user can generate the `Operation`s on the fly, they can't allocate buffers for these on the fly, all buffers must be pre-allocated. So it's not useful for, say, streaming a large transfer by reusing some small buffer repeatedly. See #367 
- Removed useless lifetimes
- Standardized buffer names on `read` and `write`, I think they're clearer.

It also specifies how i2c bus sharing is supposed to work. This is an alternative to #392 . After the discussions there, I don't think we should split I2C into Bus and Device anymore. For SPI it makes sense, because drivers want to enforce that there's a CS pin (`SpiDevice`) or not (`SpiBus`). This is not the case with I2C, the API is exactly the same in the shared and non-shared case. Drivers shouldn't care which case it is.

So all we have to do to "support" bus sharing is docs, This PR does:

- Document that it's allowed for implementations to be either shared or not.
- Document some guidelines for drivers and HALs on how to best use the traits, expand the examples.


Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
@eldruin
Copy link
Member

eldruin commented Apr 1, 2023

Thank you everybody for the discussion.
This method has been removed in #440. Closing.

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

No branches or pull requests

4 participants