Need a working example to understand DMA

Hi all,

I am trying to learn how DMA works in linux, and for that I have taken reference from this driver,

dma_test.c

I have inserted device tree entry for this inside am33xx-l4.dtsi as below,

		target-module@60000 {			/* 0x48060000, ap 36 0c.0 */
			compatible = "ti,sysc-omap2", "ti,sysc";
			reg = <0x602fc 0x4>,
			      <0x60110 0x4>,
			      <0x60114 0x4>;
			reg-names = "rev", "sysc", "syss";
			ti,sysc-mask = <(SYSC_OMAP2_CLOCKACTIVITY |
					 SYSC_OMAP2_ENAWAKEUP |
					 SYSC_OMAP2_SOFTRESET |
					 SYSC_OMAP2_AUTOIDLE)>;
			ti,sysc-sidle = <SYSC_IDLE_FORCE>,
					<SYSC_IDLE_NO>,
					<SYSC_IDLE_SMART>;
			ti,syss-mask = <1>;
			/* Domains (P, C): per_pwrdm, l4ls_clkdm */
			clocks = <&l4ls_clkctrl AM3_L4LS_MMC1_CLKCTRL 0>;
			clock-names = "fck";
			#address-cells = <1>;
			#size-cells = <1>;
			ranges = <0x0 0x60000 0x1000>;

			mmc1: mmc@0 {
				compatible = "ti,am335-sdhci";
				ti,needs-special-reset;
				dmas = <&edma_xbar 24 0 0
					&edma_xbar 25 0 0>;
				dma-names = "tx", "rx";
				interrupts = <64>;
				reg = <0x0 0x1000>;
				status = "disabled";
			};
+
+			dmatest: dmatest@0 {
+				compatible = "dma-test";
+				dmas = <&edma 20 0>;
+				dma-names = "test_chan";
+				status = "ok";
+			};
		};

after this whenever I load the driver I get below error,

# insmod dma_module.ko 
[   74.453811] dma_test 480602fc.target-module:dmatest@0: Start probe
[   74.460323] dma_test 480602fc.target-module:dmatest@0: Configure dma xfer
[   74.472296] edma 49000000.dma: Exceeded max max_burst
[   74.479823] edma 49000000.dma: cfg->src_maxburst 1, chan->device->max_burst: 0
[   74.489582] edma 49000000.dma: cfg->dst_maxburst 1, chan->device->max_burst: 0
[   74.499283] dma_test 480602fc.target-module:dmatest@0: Dmaengine slave config failed
[   74.509742] dma_test: probe of 480602fc.target-module:dmatest@0 failed with error -22
# 
#

I am not able to undestand why chan->device->max_burst is 0.
For mmc I get below logs,

[ 5535.195137] edma 49000000.dma: cfg->src_maxburst 128, chan->device->max_burst: 32767
[ 5535.202932] edma 49000000.dma: cfg->dst_maxburst 128, chan->device->max_burst: 32767

And this value 32767 is set inside edma.c file as, edma.c#L2017

s_ddev->max_burst = SZ_32K - 1;

Kindly help and guide here, I am blocked here.

bootlog,
dma-boot-log-issue.txt (40.7 KB)

Regards,
Mrigendra Chaubey

if you consider access to /dev/mem as DMA (direct memory access) … i can assist.

DMA is used to read / write memory controlled by another device, usually a processor, but could be a sensor or other device.

DMA take root access for any applications that I can think of (/dev/mem requires root access).

this link points to a project that I have made available as a disk image… it is a working example of DMA. The ‘C’ program running on the ARM processor (linux) writes to memory ‘owned’ by PRU1…

details are in the link.
good luck!
gomer

Hi

I am more interested in generic dma driver functionlity, but thanks for user space example, I’ll look into it.

One doubt, I see so many documentation on PRU dma on beaglebone black, but not a generic p2m or m2m example with edma. Why?

Regards,
Mrigendra Chaubey

what is edma in this context? I found only one CS reference, and it was for OOP development. I am assuming that p2m is processor to memory and that m2m is memory to memory… but I’m not sure about that either. Perhaps more detail on your understanding of DMA etc would get you a more informed response.

gomer

Hi

EDMA : The enhanced direct memory access (EDMA3) controller’s primary purpose is to service user-
programmed data transfers between two memory-mapped slave endpoints on the device.

EDMA is the dma controller on beaglebone’s processor.

p2m is peripheral to memory transfer
m2m is memory to memory transfer

Going through documentation and reference manual, if I succeed in making DMA to work as I want I’ll post a solution here.

Thanks & Regards.
Mrigendra Chaubey

I have written somewhat working driver for mem2mem for buffer copy without device tree using only kernel dma api as below (without error handling),

Tested on beaglebone black rev C

# uname -r
5.4.242	
#include <linux/module.h>
#include <linux/init.h>
#include <linux/dmaengine.h>
#include <linux/dma-mapping.h>

void *src,  *dst;
struct dma_chan *pChan;
static int dmaTransfer(const void *pSrc, void *pDst, unsigned int len);

static int dmaTransfer(const void *pSrc, void *pDst, unsigned int len)
{
	enum dma_status status = DMA_ERROR;
	struct dmaengine_unmap_data *pUmData;
	unsigned char align;
	unsigned int alignLen;
	struct page *pPage;
	unsigned long pgOff;
	struct dma_device *pDev;
	struct dma_async_tx_descriptor *pTx = NULL;
	dma_addr_t dmaSrcAddr, dmaDstAddr;
	dma_cookie_t dmaCookie;
	pr_info("dma4-module dmaTransfer: before dst: %d, src: %d", *(int *)dst, *(int *)src);
	dma_cap_mask_t mask;
	dma_cap_zero(mask);
	dma_cap_set(DMA_MEMCPY, mask);	

	align = 0;
	alignLen = len;

	alignLen = (alignLen >> align) << align;
	if (alignLen == 0) {
			alignLen = 1 << align;
	}
		
	pChan = dma_request_channel(mask, NULL, NULL);
	pr_info("dma4-module dmaTransfer: dma channel name : %s", dma_chan_name(pChan));
	pDev = pChan->device;
	pUmData = dmaengine_get_unmap_data(pDev->dev, 2, GFP_KERNEL);
	pPage = virt_to_page(pSrc);
	pgOff = offset_in_page(pSrc);
	pUmData->addr[0] = dma_map_page(pDev->dev, pPage, pgOff, pUmData->len, DMA_TO_DEVICE);
	dmaSrcAddr = pUmData->addr[0];
	dma_mapping_error(pDev->dev, pUmData->addr[0]);

	pPage = virt_to_page(pDst);
	pgOff = offset_in_page(pDst);
	pUmData->addr[1] = dma_map_page(pDev->dev, pPage, pgOff, pUmData->len, DMA_FROM_DEVICE);
	dmaDstAddr = pUmData->addr[1];
	dma_mapping_error(pDev->dev, pUmData->addr[1]);

	pTx = pDev->device_prep_dma_memcpy(pChan, dmaDstAddr, dmaSrcAddr, alignLen, DMA_CTRL_ACK | DMA_PREP_INTERRUPT);
	dmaCookie = pTx->tx_submit(pTx);
	dma_submit_error(dmaCookie);

	status = dma_sync_wait(pChan, dmaCookie);
	dmaengine_terminate_sync(pChan);
	if (status != DMA_COMPLETE) {
					switch (status) {
					case DMA_IN_PROGRESS:
						pr_info("dma4-module dmaTransfer: DMA in progres...");
						break;
					case DMA_PAUSED:
						pr_info("dma4-module dmaTransfer: DMA paused...");
						break;
					case DMA_ERROR:
						pr_info("dma4-module dmaTransfer: DMA error");
						break;
					default:
						;
					}
				}
				else {
					pr_info("dma4-module dmaTransfer: DMA transfer complete...");
				}
	pr_info("dma4-module dmaTransfer: after dst: %d, src: %d", *(int *)dst, *(int *)src);
	dmaengine_unmap_put(pUmData);
	dmaengine_terminate_sync(pChan);
	dma_release_channel(pChan);

	return 0;
}

static int __init mod_init(void)
{
	int *writer;
	pr_info("dma4-module: in init\n");

	src = kzalloc(16,GFP_KERNEL);
	dst = kzalloc(16,GFP_KERNEL);
	writer = (int *)src;
	*writer = 65;
	pr_info("dma4-module mod_init: before dst: %d, src: %d", *(int *)dst, *(int *)src);
	dmaTransfer(src, dst, 16);
	pr_info("dma4-module mod_init: after dst: %d, src: %d", *(int *)dst, *(int *)src);
	pr_info("dma4-module mod_init: init done\n");

	return 0;
}

static void __exit mod_exit(void)
{
    pr_info("dma4-module mod_exit: mod_exit called\n");
    kfree(src);
    kfree(dst);
    pr_info("dma4-module mod_exit: released channel");
}

module_init( mod_init );
module_exit( mod_exit );

MODULE_AUTHOR("Mrigendra.Chaubey@gmail.com");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("DMA engine api/memcpy test.");

Above module sometimes work, and sometimes it doesn’t work. Log for same,

[  471.278017] dma4-module: in init
[  471.281548] dma4-module: before dst: 0, src: 65
[  471.281577] edma 49000000.edma: private_candidate: wrong capabilities
[  471.286149] edma 49000000.edma: EER0 00000000
[  471.286168] edma 49000000.edma: Got eDMA channel 20 for virt channel 0 (SW trigger)
[  471.286180] dmaengine: __dma_request_channel: success (dma1chan0)
[  471.286186] dma4-module: dma channel name : dma1chan0
[  471.286233] edma 49000000.edma: vchan 8d3312c7: txd be193a83[9]: submitted
[  471.291413] edma 49000000.edma: first transfer starting on channel 20
[  471.291426] edma 49000000.edma: ESR0 00000000
[  471.291448] edma 49000000.edma: EER0 00000000
[  471.291461] edma 49000000.edma: Transfer completed on channel 20
[  471.291487] dma4-module: DMA transfer complete...
[  471.291505] edma 49000000.edma: EER0 00000000
[  471.296242] edma 49000000.edma: EER0 00000000
[  471.296254] edma 49000000.edma: Free eDMA channel 20 for virt channel 0
[  471.296262] dma4-module: after dst: 0, src: 65 <<-- value is not updated in dst buffer
[  471.296265] dma4-module: init done

rmmod’d module and ten again insmoded

[  638.845004] dma4-module: in init
[  638.848295] dma4-module: before dst: 0, src: 65
[  638.848324] edma 49000000.edma: private_candidate: wrong capabilities
[  638.853117] edma 49000000.edma: EER0 00000000
[  638.853139] edma 49000000.edma: Got eDMA channel 20 for virt channel 0 (SW trigger)
[  638.853152] dmaengine: __dma_request_channel: success (dma1chan0)
[  638.853159] dma4-module: dma channel name : dma1chan0
[  638.853202] edma 49000000.edma: vchan 8d3312c7: txd 2c0b7c5f[11]: submitted
[  638.858297] edma 49000000.edma: first transfer starting on channel 20
[  638.858309] edma 49000000.edma: ESR0 00000000
[  638.858330] edma 49000000.edma: EER0 00000000
[  638.858343] edma 49000000.edma: Transfer completed on channel 20
[  638.858371] dma4-module: DMA transfer complete...
[  638.858390] edma 49000000.edma: EER0 00000000
[  638.863206] edma 49000000.edma: EER0 00000000
[  638.863219] edma 49000000.edma: Free eDMA channel 20 for virt channel 0
[  638.863228] dma4-module: after dst: 65, src: 65  <<-- correct value updated in dst buffer
[  638.863232] dma4-module: init done

Any idea why sometimes I get correct buffer values (65) and sometimes don’t?

One more question how dma1chan0 becomes channel 20, is there a logic for it?

1 Like