embassy icon indicating copy to clipboard operation
embassy copied to clipboard

embassy-rp: new DMA driver

Open djdisodo opened this issue 6 months ago • 7 comments

motivation: need better way to cancel dma mid flight and read remaining transfer count

dma transfer's are built using TransferBuilder

this should support wide range of configuration, repeating read/write, ring read/ring write, etc

configuration errors are caught in compile time

djdisodo avatar Sep 01 '25 13:09 djdisodo

Tangentially related, but it would be cool to add support for PIO to PIO DMA transfers. I have a proof of concept in a branch but the API is hacky:

https://github.com/tonarino/embassy/commit/508d69869e1fe569d8372a476c1e038582ae8d73

It's great for PIO programs that are just converting from one digital format to another. Super fast data transfers and the CPU barely has to get involved.

bschwind avatar Sep 12 '25 15:09 bschwind

@bschwind i tried to make api possible to read from any &Word address and write to any &mut Word address

if work go as planned pio-to-pio dma will just work too

but i think pio fifo reg would need to be exposed as well

djdisodo avatar Sep 12 '25 16:09 djdisodo

@djdisodo that's great to hear! And yes, I think the FIFO registers would need to be exposed, though you could maybe set up an API where you don't have to expose them to users.

Something like

sm.rx().dma_to_pio(dma.reborrow(), other_sm.tx(), false).wait().await;

I haven't put too much thought into a nice API yet though.

bschwind avatar Sep 12 '25 16:09 bschwind

@bschwind

i was trying to use pio with this however pio txf/rxf isn't exposed

i think there are several ways we can expose these

  1. custom type that implements dma::Target for each tx and rx

although it's used mostly for dma

this eliminates chance of it being used for other things(if there's any)

  1. vcell since non-volatile access to those regs would cause unpredictable result it's better to force volatile access

but vcell crate itself allows mutating with immutable reference which is weird

  1. Reg<u32, R> and Reg<u32, W> provides volatile access

but current design doesn't expose pac

main issue is these are clonable

i think i have to go with 1 probably

also splitting TargetRef and TargetMut into Target/TargetRef/TargetMut

to prevent reading from txf or such where reading behavior isn't defined

djdisodo avatar Sep 13 '25 18:09 djdisodo

i was trying to use pio with this however pio txf/rxf isn't exposed

Not sure I follow completely, txf/rxf aren't exposed for users of the embassy-rp crate, but I was guessing that all this DMA work would happen within embassy itself, where you have easy access to those registers.

That being said, I haven't devoted much of my time to this to know well the details on what will work and what won't, so I have limited input to give you at the moment, sorry.

bschwind avatar Sep 22 '25 12:09 bschwind

decisions

new_with_transfer_count is not unsafe

only reason i'd consider this unsafe is that you can set TreqSel regardless of what target you use

how do you think? should it be unsafe?

you have to set transfer_count

this transfer_count is maximum bound for transfer_count and calculated transfer count will be returned

rp2040's DMA hardware requires transfer_count to be set, so it can't be infinite

by adding const value to Target that tells if it can be infinitely transferred,

we can later support rp234x's ability to do DMA without transfer_count

djdisodo avatar Oct 06 '25 17:10 djdisodo

here's example code that uses this api

fn a() -> u32 {
        let (rx, tx) = self.sm1.rx_tx();
        let buffer = UnsafeCell::from_mut(buffer);
        let read_dma_target = unsafe { buffer.as_ref_unchecked() };
        let write_dma_target = unsafe { buffer.as_mut_unchecked() };
        let (mut rxt, tcount_original) = rx.dma_read_with_transfer_count(rxc, write_dma_target, u32::MAX);
        let mut txt = tx.dma_write_with_transfer_count(txc, read_dma_target, u32::MAX).0;
        select(
            join(
                rxt.wait(),
                txt.wait(),
            ),
            self.irq_cs.wait()
        ).await;
        let tcount = rxt.transfer_count();
        drop((rxt, txt));


        self.sm1.set_enable(false);

        tcount_original - tcount

}

djdisodo avatar Oct 06 '25 17:10 djdisodo