DMA 子系统

首先补充点高端内存的知识
在32位操作系统上,把程序的0-3G(PAGE_OFFSET:0xc0000000)作为用户空间,3-4G作为内核空间.
因为每个进程有各自不同的页表,进程的用户空间是完全独立的,互不相干,但内核空间是由内核负责映射,它不会随进程的切换而切换,内核空间的虚拟地址到物理地址的映射是所有进程共享的.内核的虚拟空间独立其他程序.
内核空间的内存分配依据documentation/arm/memory.txt,如下:
在这里插入图片描述
在内核的虚拟地址空间3-4G这个范围,有一段大小为896M的虚拟内存是直接映射到0-896M的物理地址空间的.逻辑地址与物理地址之间的转化是通过加上一个偏移PAGE_OFFSET来实现的.
逻辑地址也是虚拟地址,其以一般虚拟地址的不同就是,逻辑地址采用线性映射,直接造成了逻辑地址与物理地址的一一对应.
有个问题:如果进程内核空间0xc0000000-0xffffffff(3-4G)都直接与物理内存直接一一对应,那么进程空间只能访问物理内存的1G空间,其他内存空间就没办法访问了.
为了解决这个问题,通常在32bit的内核中,将0-896M的物理地址空间直接映射到内存直接映射区域.将高于896M的物理地址映射到内核空间的高端内存区域(PKMAP_BASE–PAGE_OFFSET-1).当需要访问高于896M的物理空间时,先从高端内存区域申请一段虚拟地址,并把需要访问的物理地址映射到该段虚拟地址.这样就可以访问整个物理内存了.这种方式往往是使用alloc_page来获取内存.
同理,当我们使用vmalloc时,也可以在vmalloc区域分配一段虚拟地址空间来映射到物理高端内存.通过这种方式也可以访问物理高端内存.
在这里插入图片描述总结访问高端内存的2中方式:alloc_page和vmalloc

在ARM架构的SOC中,可能会存在DMA寻找问题,即DMA无法访问所有的物理地址空间.只能访问特定的物理地址空间.一般的,DMA区域位于内存直接映射区域中的DMA区域.其和物理内存的某一块DMA区域对应.在这里插入图片描述

我们写程序的时候,经常遇到memory和外设device之后的数据交互,通常的做法是通过CPU处理,但是如果需要重复传输大量数据,势必会导致CPU效率低,能不能有一个硬件模块独立干这个数据传输的事情呢?程序员只需要告诉这个硬件设备,需要传输的数据的源头和目的地,存储地址和长度,它就能欢快的跑下去,最好锁要求的数据传输完成后,能主动的通知CPU,那就更好了.

DMA就是这样的外设,DMA就像普通设备那样一样,提供一组控制/状态/数据/中断寄存器,通过寄存器来驱动DMA工作.

DMA在Linux kernel中,和IIC,SPI,等一样,也分控制器和外设的概念,我们视作为资源的提供者(provider,DMA控制器),和资源的消费者(client,具体的需要用DMA的外设).
所谓provide,就是DMA控制器,它直接访问CPU寄存器,提供DMA通道,但不提供用户态的调用接口或者文件节点,而是提供一套API,供client端申请/操作DMA资源.

DMA并不是独立工作,而是要和其他驱动模块结合,实现数据传输的工作.比如,音频codec和memory之间的数据交互,UART和memory之间的数据,显示设备和memory之间的数据传输.

Linux kernel实现了dmaengine,封装了DMA控制器和DMA外设,规范了DMA控制器的接口(本地回调函数),屏蔽了控制器的实现细节,实现控制器与设备驱动的独立和分离,同时DMA的中断也由DMA控制器接管.
Linux kernel将DMA控制器抽象为struct dma_device.具体的SOC也可能会再封装一层(比如rk3288使用的dmac pl330就重新封装为struct pl330_dmac)

一个DMA controller可以"同时"(宏观上的同时,微观上的串行)进行的DMA传输的个数是有限的,这个正在传输的数据通道称为 DMA channels.一个DMA控制器,宏观上由多个硬件上的channel.软件上在基于这个硬件channel,在抽象出虚拟的channel.
DMA传输由CPU发起,CPU会告诉DMA控制器,指定数据的源地址和目的地址.CPU发出请求后,DMA控制器根据CPU给的配置,完成数据传输任务.
DMA传输可以分为四类:memory到memory,memory到device,device到device,device到memory.
memory到memroy的传输,又称为Async TX.其他三者又称为slava-DMA

设备驱动的本质是描述并抽象硬件,然后为consumer提供操作硬件的友好接口.DMA控制器,也不例外,其要做的事:
1.抽象并控制DMA控制器.
2.管理DMA channel(可以是物理的,也可以是虚拟的.),并提供友好的接口.
3.以DMA channel为操作对象,响应client driver(consumer)的传输请求,并控制DMA controller执行传输.

Linux kernel使用struct dma_device抽象DMA controller,driver只需要填充该结构中的必要的字段,就可以完成dma controller的驱动开发.struct dma_device是各庞大的数据结构,含有必需本地实现的和硬件相关的回调函数.
使用struct dma_chan抽象物理的DMA channel.物理channel和控制器所能提供的通道数一一对应.多个虚拟chan可以共享一个物理channel.并在这个物理channel上进行分时传输.

因此,dma 控制器驱动的开发主要工作如下:
1.定义一个struct dma_device变量.并根据实际的硬件情况,填充其中的关键字段和回调函数.
2.更具controller支持的channel个数,为每个chan定义一个struct dma_chan变量,进行必要的初始化后,将每个channel都添加到struct dma_device变量的channels链表中.
3.根据硬件特性,实现struct dma_device变量中的必要的回调函数(device_alloc_chan_resource/device_free_chan_resource,device_prep_dma_xx,device_config,device_issure_pending等::这些函数分别在device驱动申请chan,配置等时被调用)
4.调用dma_async_devcie_register将struct dma_device注册到kernel中.

以rk3288的中断控制器pl330.c为例
dts中定义:
dmac_peri: dma-controller@ff250000 {
compatible = “arm,pl330”, “arm,primecell”;
reg = <0x0 0xff250000 0x0 0x4000>;
interrupts = <GIC_SPI 2 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 3 IRQ_TYPE_LEVEL_HIGH>;
#dma-cells = <1>;
arm,pl330-broken-no-flushp;
peripherals-req-type-burst;
clocks = <&cru ACLK_DMAC2>;
clock-names = “apb_pclk”;
};
pl330.c分析(dma 控制器驱动因SOC厂家不同,可能差异非常大,验证以来SOC设计)
驱动之实现了必须的probe和remove方法.
probe中申请dma_device后,首先获取了dts硬件的资源,申请中断资源,tasklet_init,定义必须的回调函数接口,后,被dma_async_device_register注册.
第二步,就是实现这些本地回调函数.

DMA总线的注册在dmaengine.c中提供dma控制器和devie的注册等api.

设备端:
对于设备来说,要基于DMA engine提供的Slave-DMA进行DMA传输的话.需要如下的操作步骤:
1.申请一个DMA channel(dma_request_chan)
2.根据设备(Slave)的特性,配置DMA channel的参数(struct dma_slave_config),然后调用dmaengine_slave_config发给DMA控制器.
3.获取一个用于识别本次传输的描述符(tx descriptor),slave driver需要将此次的一些信息(src/dst的buffer,传输方向,数据宽度,size等)提交给DMA engine.dma engine确认okay后,返回一个描述符(struct dma_async_tx_descriptor抽象).slave驱动根据这个描述符控制并跟踪此次传输.(dmaengine_prep_slave_sg/dmaengine_prep_dma_cyclic/dmaengine_prep_interleaved_dma)
4.将本次传输提交给dma engine并启动传输(提交dmaengine_submit,启动传输dma_async_issue_pending)
5.等待传输结束.传输完成后,可以通过中断函数获取传输完成的消息.
然后,重复33-5步

以spi控制器中使用dma为例.
dts中的定义
spi0: spi@ff110000 {
compatible = “rockchip,rk3288-spi”, “rockchip,rk3066-spi”;

dmas = <&dmac_peri 11>, <&dmac_peri 12>;
dma-names = “tx”, “rx”;

};
spi-rockchip.c的probe中关于dma的实现.
static int rockchip_spi_probe(struct platform_device *pdev)
{
rs->dma_tx.ch = dma_request_slave_channel(rs->dev, “tx”);//申请dma chan
rs->dma_rx.ch = dma_request_slave_channel(rs->dev, “rx”);

if (rs->dma_tx.ch && rs->dma_rx.ch) {
	rs->dma_tx.addr = (dma_addr_t)(mem->start + ROCKCHIP_SPI_TXDR);//初始化config
	rs->dma_rx.addr = (dma_addr_t)(mem->start + ROCKCHIP_SPI_RXDR);
	rs->dma_tx.direction = DMA_MEM_TO_DEV;
	rs->dma_rx.direction = DMA_DEV_TO_MEM;

	master->can_dma = rockchip_spi_can_dma;
	master->dma_tx = rs->dma_tx.ch;
	master->dma_rx = rs->dma_rx.ch;
}

}
probe中对于DMA,只根据dts中的定义,申请了dma_chan,并指定tx/rx buff和方向等.
然后对dma的具体操作在spi的transer回调函数
rockchip_spi_transfer_one
{
if (rs->use_dma) {
if (rs->tmode == CR0_XFM_RO) {
/* rx: dma must be prepared first /
ret = rockchip_spi_prepare_dma(rs);
spi_enable_chip(rs, 1);
} else {
/
tx or tr: spi must be enabled first */
spi_enable_chip(rs, 1);
ret = rockchip_spi_prepare_dma(rs);
}
}
}
在rockchip_spi_prepare_dma中实现config发送给DMA控制器,以及根本发送传输参数并得到传输描述符,然后定义传输完成的回调函数.然后调用dmaengine_submit提交请求,并dma_async_issue_pending()启动传输.
rockchip_spi_prepare_dma
{
if (rs->rx) {
rxconf.direction = rs->dma_rx.direction;
rxconf.src_addr = rs->dma_rx.addr;
rxconf.src_addr_width = rs->n_bytes;
rxconf.src_maxburst = 1;
dmaengine_slave_config(rs->dma_rx.ch, &rxconf);

	rxdesc = dmaengine_prep_slave_sg(
			rs->dma_rx.ch,
			rs->rx_sg.sgl, rs->rx_sg.nents,
			rs->dma_rx.direction, DMA_PREP_INTERRUPT);
	if (!rxdesc)
		return -EINVAL;

	rxdesc->callback = rockchip_spi_dma_rxcb;
	rxdesc->callback_param = rs;
 }
 	if (rxdesc) {
	spin_lock_irqsave(&rs->lock, flags);
	rs->state |= RXBUSY;
	spin_unlock_irqrestore(&rs->lock, flags);
	dmaengine_submit(rxdesc);
	dma_async_issue_pending(rs->dma_rx.ch);
}

}

综合来看,dma框架,看上去代码不多,但是很多细节,细节还和具体的SOC设计联系紧密,具体使用中要考虑很多,比如数据一致性.

https://www.jianshu.com/p/e1b622234d13

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
Linux 内核的 DMA 子系统是一种提供直接内存访问(Direct Memory Access,DMA)功能的机制,可以实现设备之间的数据传输,减少 CPU 的负担,提高系统性能。DMA 子系统包括了 DMA 控制器、DMA 缓冲区管理、中断处理等多个部分,其中比较重要的 API 包括以下几个: 1. dma_alloc_coherent():分配 DMA 缓冲区的 API,用于分配一段连续的物理内存,用于 DMA 数据传输。 2. dma_map_single() 和 dma_unmap_single():将一个单独的 DMA 缓冲区映射到物理地址空间,并在 DMA 操作完成后释放映射。 3. dma_sync_single_for_cpu() 和 dma_sync_single_for_device():同步 DMA 缓冲区和物理地址空间之间的数据,以确保数据的正确性和一致性。 4. dmaengine_submit() 和 dma_async_issue_pending():提交 DMA 操作请求,启动 DMA 数据传输。 5. dmaengine_terminate_all():停止所有正在进行的 DMA 操作。 DMA 的工作原理是通过 DMA 控制器实现的。控制器可以直接访问系统总线,将数据从设备中读取到 DMA 缓冲区中,或者从 DMA 缓冲区中将数据传输到设备中。在进行 DMA 操作时,需要设置 DMA 控制器的寄存器,包括源地址、目的地址、传输长度等信息。DMA 控制器会负责处理数据传输的过程,传输完成后会产生中断信号通知 CPU。 需要注意的是,Linux 内核的 DMA 子系统需要硬件支持,即需要设备具有 DMA 控制器接口。如果设备没有 DMA 控制器接口,也可以通过 CPU 进行数据传输,但效率会降低。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

九月天-深圳专业软硬件开发

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值