dma-theory-linux

 1) Requesting a DMA channel: dma_request_channel(const dma_cap_mask_t *mask,dma_filter_fn fn, void *fn_param);

2)Configurintg the DMA channel: dmaengine_slave_config(struct dma_chan *chan,struct dma_slave_config *config);

2)Configuring the DMA transfer: dmaengine_prep_dma_memcpy(my_dma_chan,dma_dst_addr, dma_src_addr, BUFFER_SIZE, 0);

3)Submitting the DMA transfer: dmaengine_submit(struct dma_async_tx_descriptor *desc);

4)Issue pending DMA requests and wait for callback dma_async_issue_pending(struct dma_chan *chan);




https://elixir.bootlin.com/linux/latest/source/drivers/dma/sh/rcar-dmac.c

https://android.googlesource.com/kernel/msm/+/android-msm-marlin-3.18-nougat-dr1/drivers/dma/imx-sdma.c

----------------------------------------------------------------------

The direct memory access (DMA) is a feature that allows some hardware subsystems to access memory independently from the central processing unit (CPU).


The DMA can transfer data between peripherals and memory or between memory and memory.


Request a DMA channel:

first declare dma channels to the peripherals



i2s2: audio-controller@4000b000 {

compatible = "st,stm32h7-i2s";

#sound-dai-cells = <0>;

reg = <0x4000b000 0x400>;

interrupts = <GIC_SPI 37 IRQ_TYPE_LEVEL_HIGH>;

dmas = <&dmamux1 39 0x400 0x01>,

       <&dmamux1 40 0x400 0x01>;

dma-names = "rx", "tx";

status = "disabled";

};


then in driver,  dmaengine finds a channel that matches the configuration specified in the dmas property as below,


struct dma_chan *chan_rx, *chan_tx;


chan_rx =  dma_request_chan(&pdev->dev, "rx");

chan_tx = dma_request_chan(&pdev->dev, "tx");


Configure the DMA channel:

Source/Destination addresses, Source/Destination address width, Source/Destination maximum burst are used by the

DMA controller driver to configure the channel. The user must use dmaengine_slave_config() to set this dma_slave_config structure in the DMA controller driver.


/* In case of memory to device (TX) */

memset(&config, 0, sizeof(config));

config.dst_addr = phy_addr + txdr_offset;

config.dst_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;

config.dst_maxburst = 1;

config.direction = DMA_MEM_TO_DEV;


/* In case of device to memory (RX/Capture) */

memset(&config, 0, sizeof(config));

config.src_addr = phy_addr + rxdr_offset;

config.src_addr_width = DMA_SLAVE_BUSWIDTH_1_BYTE;

config.src_maxburst = 1;

config.direction = DMA_DEV_TO_MEM;


Configure the DMA transfer:


The DMA engine transfer API must be used to prepare the DMA transfer. Three modes are supported by STM32 DMA controller drivers:


slave_sg: prepares a transfer of a list of scatter-gather buffer from/to a peripheral

dma_cyclic: prepares a cyclic operation from/to a peripheral until the operation is stopped by the user

dma_memcpy: prepares a memcpy operation (seldom used except by dmatest)


struct dma_async_tx_descriptor *txdesc;


txdesc = dmaengine_prep_...

txdesc->callback = peripheral_driver_dma_callback;

txdesc->callback_param = peripheral_dev;


Submit the DMA transfer:


Once the transfer is prepared, it can be submitted for execution. It is added to the pending queue using dmaengine_submit() used as parameter of dma_submit_error() to digest the returned value.


dma_cookie_t dmaengine_submit(struct dma_async_tx_descriptor *desc)

static inline int dma_submit_error(dma_cookie_t cookie)


ret = dma_submit_error(dmaengine_submit(desc));


The transfer can then be started using dma_async_issue_pending(). If the channel is idle, the first transfer in the queue is started.

void dma_async_issue_pending(struct dma_chan *chan);


On completion of each DMA transfer, a DMA interrupt is raised, then the next transfer in the queue is started and a tasklet is triggered. 

When scheduled, this tasklet calls the peripheral driver completion callback, provided it is set.


Terminate the DMA transfer:

Two variants are available to force the DMA channel to stop an ongoing transfer. No completion callback is called for an incomplete transfer and the data in DMA controller FIFO may be lost. Refer to the DMA Engine API Guide[3] for more details.


dmaengine_terminate_async(): //this function can be called from atomic context or from within a completion callback;

dmaengine_terminate_sync(): //this function must not be called from atomic context or from within a completion callback.

int dmaengine_terminate_sync(struct dma_chan *chan)

int dmaengine_terminate_async(struct dma_chan *chan)


dmaengine_synchronize() must be used after dmaengine_terminate_async() and outside atomic context or completion callback, 

to synchronize the termination of the DMA channel with the current context. The function waits for the completion of the ongoing transfer and any callback before returning.


void dmaengine_synchronize(struct dma_chan *chan)


Release the DMA channel:

The peripheral driver can ask for new transfers or simply release the channel if it is no more needed. It is typically done by calling the peripheral driver remove() function.


void dma_release_channel(struct dma_chan *chan)

-------------------------------------------------------------


DMA and cached


dma_alloc_coherent allocate physically contigouus memory which is marked as nocache. So cpu always access this data from main memory


https://stackoverflow.com/questions/69739646/linux-streaming-dma-explicit-flush-invalidate

dma_map_single, dma_sync_single_for_device, dma_sync_single_for_cpu internally take care of cache invalidation.

When using the Streaming DMA API, one does NOT have to explicitly flush or invalidate the cachelines. Functions dma_map_single(), dma_sync_single_for_device(), dma_unmap_single(), dma_sync_single_for_cpu() take care of that for you.

Comments

Popular posts from this blog

dev_get_platdata understanding

Getting started with pinctrl subsystem linux

How to take systrace in android