rtic
rtic copied to clipboard
Moving resources into `idle`
(I'm not sure if this is the right place for feature requests that are not formal RFCs, or if I should rather open an issue in the RFC repo. Please let me know, if you want me to move this.)
RTIC provides resources that a context has exclusive access to as mutable references. This makes sense for interrupt handlers, but (unless I misunderstand something) it is not the only option for the idle function: In principle, resources that idle has exclusive access to could be moved there outright.
Usually this doesn't matter, but there are APIs that require you to move values into and out of them. One such case I encountered is a DMA API that requires me to move a Channel struct into the Transfer struct, while the transfer is going on. Unless I'm missing something (I couldn't find anything related in the documentation), I can't use this API in my idle function (without further complications), because I only have a mutable reference to the Channel.
For now I'll just use Option::take to work around this problem, so this isn't very high-priority to me. Still, it would be nice to have this.
Now that I think about it, it should also be useful and possible to move resources into the interrupt context, as long as they're moved back out.
Something like this:
#[task(binds = DMA0, resources = [transfer])]
fn dma0(cx: dma_int::Context) -> dma_int::Context {
// Context contains a transfer that is in progress, which we're handling
// the interrupt for. `wait` transforms the type state, thus requires us to
// move out of the context.
let transfer = cx.resources.transfer.wait();
// TODO: Process received data here.
// Prime the transfer again. `start` transforms the type state back to what
// it originally was.
cx.resources.transfer = transfer.start();
cx
}
Again, nothing that can't be emulated with Option::take, and I'm not sure if adding this as an option would be worth the complexity. I just want to leave it here as further food for though.
Hi,
When it comes to type states they don't really play nice with RTIC as of today (as you have seen).
What I usually do is wrap the type-state in an enum and use the following code to achieve what you are looking to do (with the replace_with crate):
pub fn change<F>(&mut self, mut closure: F)
where
F: FnMut(Self) -> Self,
{
unsafe {
replace_with::replace_with_or_abort_unchecked(self, |val| closure(val));
}
}
This allows the following usage, lets say for an RF chip:
/// Wrapping enum for type-states
pub enum Wrapper<...> {
Uninitialized(Chip<Uninitialized, ...>),
Sleeping(Chip<Sleeping, ...>),
Idle(Chip<Idle, ...>),
Sending(Chip<Sending, ...>),
Receiving(Chip<Receiving, ...>),
}
// ... usage bellow
self.change(|wrapper| match wrapper {
Wrapper::Receiving(driver) => driver.finish_receiving().into(),
Wrapper::Sending(driver) => driver.finish_sending().into(),
_ => wrapper,
});
I just came across this issue while randomly browsing. On Matrix, @jamesmunns recently suggested moving resources into idle. And I had a similar revelation, that resources could be moved into tasks, as long as they get moved back out.
It's not necessary, but I think it would be a nice add-on feature for RTIC to support this. It would give RTIC more expressive power, allowing it to match the semantics of the corresponding resource. Otherwise, you have to use the shoe-horn approach of replace_with.
This sounds like a reasonable extension to RTIC.
-
To my understanding a "move" resource cannot be shared between tasks. With the
replace_withapproach that is possible. -
We need some syntax to define a resource as being "moved", perhaps mark it by
#[move]in the declaration. -
For
idlemoving in would amount to the field in the context actually containing the initial state of the moved resource. I think it may be possible to do this totally on the stack, so no backing heap representation of the moved resource. -
For tasks, it is similar BUT the moved resource(s) would need to be returned in their initial state(s). Not sure if it is that useful at the moment. However, if we implement async/await for tasks, this might be more useful. It might be possible to capture the type state in the context of the async/await "state machine" (without backing heap storage), but it depends on how we choose to implement async/await. In general its still a no-go until we can allocate storage for async/await context statically (not yet stabilized as far as I know).
With async 0 priority tasks this is now supported.