-
Notifications
You must be signed in to change notification settings - Fork 229
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
Add asynchronous versions of most embedded-hal traits using GATs #285
Conversation
Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @ryankurte (or someone else) soon. If any changes to this PR are deemed necessary, please add them as extra commits. This ensures that the reviewer can see what has changed since they last reviewed the code. Due to the way GitHub handles out-of-date commits, this should also make it reasonably obvious what issues have or haven't been addressed. Large or tricky changes may require several passes of review and changes. Please see the contribution instructions for more information. |
Nice to see more interest on async! :) Some general feedback: 1. Can't use DMAI see the proposed traits are modeled after the existing Note that
However doing DMA with "naked" slices is unsound but only if the futures are leaked. Since DMA is reading/writing directly from the slice, DMA has to be stopped if the future is dropped mid-operation. This is easy, embassy does that no problem. However in Rust you can leak things (not run Drop) without unsafe. If you do that, DMA will not be stopped and can keep writing to the buffer after it's deallocated. Detailed explanation of the problem here. To fully solve this, the traits would have to use Embassy "solves" it by forbidding the user to leak futures. Dropping them is fine, leaking them is not. This means embassy's API is not fully safe, but in practice it's really really easy to use it correctly, since there's only a handful of ways to leak things ( The third solution would be to fix the Leak Problem in Rust itself. IMO the Leak Problem is a Rust design issue. There's been some discussions, it might be fixable with a And no, Pin doesn't prevent the leak problem. Pinning the buffer doesn't. Pinning the future doesn't. 2. Maybe not add all traitsI'd wait until there's clear demand of traits before adding them.
3. LifetimesLifetimes are indeed complicated. If we drop this requirement then it can be simplified (I think that bound can be simply removed), but I would like to keep it. It's nice that drivers can be non- (I just wanted to point out the reason behind these bounds, since it's not very obvious). 4. Iterator traitsI think iterator-based traits like |
I agree with this. And I'm personally fine with an unsafe API to improve the ergonomics, as long as it's easy to use safely. I'm unsure if |
…_impl_trait feature as a result
hey thanks for the PR! looks generally good, and async is going to be great fun when we get it worked out ^_^ i'm in agreement with @eldruin here (#172 (comment)), the GAT approach seems workable to me, definitely need to demonstrate / prove each of them in a couple of use cases (linux-embedded-hal may be one good HAL candidate as there's already some async in the sub-projects). @Dirbaio also makes some great points, perhaps we could focus on
this approach is what i would expect / seems reasonable to me in that it should be implementable with or without DMA, i don't think it'd be useful to expose the one caveat would seem to be UART, to me the UART read/write would ideally be represented as a stream/sink, though i understand these aren't yet in core. i would be okay with a i think UART differs from the other peripherals because the transactions are unbounded, it seems like circular buffering / DMA in this case is a configuration / implementation detail that shouldn't need to be exposed to HAL consumers. i can see an argument for a slice based write to avoid the byte-wise copy/flush (and improve the ergonomics of the internal buffer), but it doesn't seem like there's an unsurprising way to implement a serial read via slice without pushing complexity up to users. it also doesn't seem like there's an obvious/ergonomic way to address wakers at the moment (#172 (comment)), but the proposed wake-every-poll approach would seem to do the job for now. For reference, couple of prior works on the topic (that i think y'all have already mentioned, just useful to have a list):
|
These have the same problem as per-word traits: you need CPU involvement for each word, you can eg write a big slice with DMA. Maybe it could be done with a stream/sink of slices, but that gets ugly fast.
"Read a slice of words" is the lowest common denominator of what most hardware out there can do, while not sacrificing much: It allows efficient DMA use if you know how much data you're going to receive upfront. |
yeah, though they'd work well implemented on top of IRQs or DMA and a ring-buffer, i think the ideals differ a bit depending on whether you look at this as an abstraction for physical UART peripherals vs. a UART abstraction for drivers to use.
seems alright so long as we're clear that any call might return a partially filled slice (say you had a |
I would like to note that if we only fill the received array partially, we need to return the number of words read. Alternatively, return a slice to the input array, although this would result in an interface like #286. |
In practice that would degenerate to byte-by-byte read anyway. The future would have to complete as soon as 1 byte has been received, and while the CPU is processing that byte no more bytes would be read. The hardware FIFO for UARTs is typically tiny, usually just 1..4 bytes. The "allow short writes" solution makes a lot of sense when the IO is backed by a big buffer, and read calls can copy big chunks off it. That's what
Embassy also has a It really depends on the use case. I don't think there's One Uart Trait To Rule Them All.
Also, having generic TLDR:
|
Now that GATs are no longer an incomplete feature, it's looking likely that they'll land before the end of the year! Maybe it's time to get this in as an |
Maybe just |
We have decided against an |
I propose to hide them under |
@lachlansneff I see there is one unresolved comment from @Dirbaio. I volunteer continuing this work if you don't have time for it. @Dirbaio From embassy POV, do you think these traits look sufficiently complete that we can give a go at making the embassy implementations support them? (I can make a PR for it) |
@lulf Yes, the traits here look great and I don't think there will be any issue switching embassy to them. It'd be great to do it, it'll help test the traits :) I have one major concern about merging this PR: async fn in traits is coming. It'll allow using actual IMO we should release this in a separate crate, for example Gating the async traits behind an |
I'll finish making those changes today or tomorrow. I'm with @Dirbaio on this: a separate crate If anyone has an opinion about the name, I'm fine with either. |
|
I've had the following issue with SPI using embassy: I want to write out bits to a display as quickly as possible (since there are a lot to write and my SPI is not that fast). To do this, I want to use 2 buffers for my DMA. I want one buffer to be DMAd out to the peripheral while I fill the other. Now a difference between async fn and futures with GATs is that the futures are a concrete type. This type could have a method like So basically what I'm saying is that I'd like a concrete type for the future, and then a method like |
@lachlansneff I think starting with the embedded-hal-async is nice. What is the preferred way to go about this? Merge this PR and reorganize the e-h into two crates then or do we want to see that reorg happening in this PR? |
Sorry, the semester just finished, so I didn't have time to set this up. I have it half set up on my laptop. Was thinking to just create the crate and drop this PR. |
given the fundamentals (generic type bounds for now, async trait one day) are pretty clear now i wonder whether it might be useful for us (@rust-embedded/hal) to create the |
I've opened a PR adding |
This can probably be closed, now that we have |
Indeed. Thank you everybody! |
GATs (generic associated types), among other things let you return futures from trait methods.
The lifetimes can be a bit tricky but it results in a pretty clean trait.
For example:
It's important to note that GATs are only available on nightly at the moment, so this uses feature attributes. I'd like to discuss how this should be exposed (likely through a feature until GATs are stabilized).
Also, there are some traits that don't use futures that are duplicated in the
nb
,blocking
, andfutures
modules. They should probably be merged.