初识DMA(1)

本文介绍了DMA的基本概念,包括数据搬运的作用以及在Linux系统下的DMA框架。硬件操作部分详细阐述了DMA控制器的工作原理,如通道选择、DRQ信号线的作用。软件操作则提到了Linux DMA Engine框架,描述了DMA控制器驱动和虚拟通道的概念。此外,文章还讨论了DMA的块传输和散列传输方式,并重点介绍了DMA descriptor在散列传输中的应用。
摘要由CSDN通过智能技术生成

前言

项目做多了,会看到很多关于DMA的操作,比如

  1. 音频数据传输。
  2. SPI数据传输(spi-oled屏幕,传输图像)。

之所以列举出上面两项,是想在之后的章节重点介绍一下。
关于DMA,其实就是数据的搬运(mem->mem, dev->mem, mem->dev)。感觉很简单,但是看代码后,发现linux提供的DMA接口并不是很简单,所以想通过一系列的文章,来打通DMA脉络,了解DMA api的使用以及背后的意义,从而在遇到DMA相关操作时,不会那么的陌生,并且很快的能融入开发当中。本系列将以全志平台为背景介绍DMA。

DMA的硬件操作

关于DMA的操作,其实就是对DMA controller的操作,在controller上,有很多独立的DMA 通道(channel)提供选择,所谓通道就是数据传输的隧道。
比如内存与设备之间的数据传输,要通过DMA其中一个通道进行传输,即DMA controller会自动从内存中取数据(源),通过选好的通道(软件指定)传输到设备上(目的地),当然这些通道都是独立的。 另外、这里涉及到了3个硬件对象,即:内存<–>DMA<—>设备(假设是SPI),三者之间的传输的配置大概是:

  • 源指定:内存
    指定数据所在的内存地址给DMA , 并且告诉DMA,DMA<->DRAM的DRQ的编号(关于DRQ,稍后会提到)。
  • 目的地指定: SPI设备
    指定SPI设备接收数据的地址(可能是某个RX寄存器,或者TX寄存器)并且告诉DMA, DMA<-> SPI之间DRQ的编号。
    DRQ,是设备与DMA之间的信号线,连接到 DMA的port上。
    我们来看一下 DMA controller的 block 图。
    在这里插入图片描述
    上图所示,DMA controller 上面有很多的port用于 与各个设备进行链接(物理上的),每一条链接叫做DRQ,它是一条信号线。
    上面的例子中,内存与SPI之间传输数据,就要激活DMA与DRAM的DRQ,以及DMA与SPI设备之间的DRQ。
    DRQ的作用是:在设备(MEM&SPI)与DMA之间的数据传输中,需要类似于TCP/IP那样的传输协议来确保数据的可靠性(比如握手协议),这些协议需要操作DRQ信号线(拉高拉低),来进行数据方面的同步。
    我们在配置DMA传输时,只需要告诉DMA要用到哪个DRQ,DMA会自动去控制它。
    DRQ信号线的标号,是通过 port上的硬件标号来获取到的,如下图:
    在这里插入图片描述
    我们可以看到 SDRAM链接到DMA controller的port0端口,即DRQ的type就是0,
    DMA与SPI之间的是 port22, port23,即DRQ的值就是 22,23。
    细心的同学可能注意到 DRQ type 有 source跟 Destination之分,其实就是传输的方向问题,不管方向是什么,都是通过唯一的port来绑定的,比如本例的传输路线如下所示:
    在这里插入图片描述
    即,此传输,port0链接端点 是 Source DRQ type的,DRQ的值为0;
    port22(SPI0)链接端点 是 Destination DRQ type的, DRQ的值为22;

DMA的软件操作

软件框架(framework)

对于DMA controller的控制,在linux下有一套管理,叫做 Linux DMA Engine framework,这个 framework实际上,就是提供了一系列操作 DMA controller 的API (controller的通道的选择等),这些API提供给 client driver (SPI driver)用来操作DMA,我们会在下一节会详细展framework的结构,目前只介绍大体脉络,让我们对DMA的结构有个快速的认识。
DMA的软件框图如下(从网上拿的图片):
在这里插入图片描述

可以看到,CH0->CHn 对应DMA controller里的硬件通道,在driver(这里的driver是指 DMA controller driver)中每个硬件通道都被抽象成一个结构体。比如: CH0<—>DC0, CH1<—>DC1等,与 硬件通道一一对应。
接下来上层进一步封装了虚拟通道(vchan),这些虚拟通道,多个虚拟通道会对应一个真实的硬件通道DC0,从而让client driver看起来 有很多的DMA通道可以用。
但实际上,经过代码分析,不管有多少个client driver 同时用DMA,即使申请不同的DCn,也不会像想象中的那样并行执行数据传输任务,实际做法是把任务都 扔到一个等待队列给DMA framework,framework会从队列中一个一个的去处理job,即排队处理,并不会想象中的并发执行job,所以感觉vchan的设计好像没有什么用。另外,在全志平台中 一个 vchan是对应一个DC0,即虚拟通道与 物理通道是一一对应的关系。

DMA的传输方式
  1. DMA块传输方式
    DMA数据传输可分为块传输和散列传输两种方式。在DMA传输数据的过程中,要求源物理地址必须是连续的。但是在某些情况下,连续的存储器地址在物理上不一定是连续的,所以DMA传输要分成多次完成。传输完一块物理上连续的数据后引发一次中断,然后进行下一块物理上连续的数据传输,这就是DMA块传输方式(Block DMA)。
  2. 散列传输
    散列传输是在块传输方式上发展起来的,需要硬件的支持,它与一个传输链表相关。该链表可以是单向结构或环形结构。链表是由叫做Descriptor information(也叫DMA descriptor)的链表项组成,一个传输任务对应一项DMA descriptor,之后我们只需要指定第一个 Descriptor information项的地址, 即链表第一项的地址给DMA,DMA就会自动把所有链表项中所描述的任务都执行完。
DMA descriptor

因为全志平台用的是 散列传输模式(比较灵活),所以重点介绍一下。我们通过一个实例,来说明这个链表是如何组成并且被DMA执行的。
比如说我们想通过DMA把数据从内存给SPI,一共8K的数据,我们用vmalloc函数申请内存空间,因为vmalloc只保证虚拟地址空间是连续的,所以这些数据在内存上是不连续的,因此我们要:

  1. 将 8k内存所对应的连续物理空间找出来, 即2页的数据,并且这两页并不是连着的(start_phy1->end1, start_phy2->end2)
  2. 将上面两部分的内存范围填入 DMA descriptor结构体,组成 descriptor1, descriptor2,两个链表项。
  3. 将 descriptor1 的地址传给DMA,进行传输。

接下来,我们看一下 DMA Descriptror的描述(取自 datasheet)
在这里插入图片描述
上面的说明很详细了,我们接着上面的例子,全志平台中DMA descriptor对应的是struct sunxi_dma_lli 结构体,我们看一下如何填充此结构体,因为需要填充两个,首先是:start_phy1->end1的地址范围。

#define sunxi_slave_id(d, s) (((d)<<16) | (s))
#define GET_SRC_DRQ(x)	((x) & 0x000000ff)
#define GET_DST_DRQ(x)	((x) & 0x00ff0000)

#define DRQSRC_SDRAM        0
#define DRQDST_SPI0TX		22
#define DRQDST_SPI1TX       23
#define SUNXI_SPI_DRQ_RX(ch) 		(DRQSRC_SPI0RX + ch)
#define SUNXI_SPI_DRQ_TX(ch) 		(DRQDST_SPI0TX + ch)

struct sunxi_dma_lli *lli = malloc;
lli->src = (u32)start_phy1;
//为什么是TX
// 因为要从SPI0 HOST 的TX寄存器传数据给OLED。
// 所以DMA要把数据传给TX寄存器,TX寄存器再把数据发送给OLED
lli->dst = (u32)SPI_TX //Transmit Data;
lli->len = end1 - start_phy1;
lli->para = NORMAL_WAIT;
lli->cfg |= SRC_LINEAR_MODE
	| DST_LINEAR_MODE
	| GET_DST_DRQ(sconfig->slave_id); //告诉DMA传输的时候,要用到 DMA->SPI之间的DRQ,之后传输开始后,DMA会自动控制指定的DRQ信号线。
	| GET_SRC_DRQ(sconfig->slave_id); //告诉DMA传输的时候,要用到 DMA->DRAM之间的DRQ,之后传输开始后,DMA会自动控制指定的DRQ信号线。
//spi driver:
//slave_id = sunxi_slave_id(SUNXI_SPI_DRQ_TX(sspi->master->bus_num), DRQSRC_SDRAM);

紧接着对于:start_phy2->end2 我们做同样的事情:

struct sunxi_dma_lli *lli2 = malloc;
...
...

接下来我们设置链表

lli->p_lln = lli2;
lli2->p_lln = 0xFFFFF800;

即第一个DMA descriptor指向第二个 DMA descriptor
第二个 指向 0xFFFFF800(意味着结束)。
之后将 lli->p_lln 写入 DMA 中

#define CHAN_START	1
#define CHAN_STOP	0

/* write the first lli address to register, and start to transfer */
//txd->lli_phys: lli的物理地址
writel(txd->lli_phys, sdev->base + DMA_LLI_ADDR(chan_num));
writel(CHAN_START, sdev->base + DMA_ENABLE(chan_num));

//sdev->base + DMA_LLI_ADDR(chan_num)
在这里插入图片描述
//sdev->base + DMA_ENABLE(chan_num)
在这里插入图片描述
DMA启动后,DMA就会自动读取链表中的第一项,传输完成后会产生一个中断,然后紧接着自动读取第二个项进行传输, 第二项传输完了,发现没有第三项(因为第二项的p_lln 是0xFFFFF800)
为了对照,我们看一个形象的图。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值