直接内存访问(DMA)

1. 什么是DMA

直接内存访问是一种硬件机制,它允许外围设备和主内存之间直接传输它们的I/O数据,而不需要系统处理器的参与。使用这种机制可以大大提高与设备通信的吞吐量。

 

2. DMA数据传输

有两种方式引发数据传输:

第一种情况:软件对数据的请求

1. 当进程调用read,驱动程序函数分配一个DMA缓冲区,并让硬件将数据传输到这个缓冲区中。进程处于睡眠状态。

2. 硬件将数据写入到DMA缓冲区中,当写入完毕,产生一个中断

3. 中断处理程序获取输入的数据,应答中断,并唤起进程,该进程现在即可读取数据

第二种情况发生在异步使用DMA时。

1. 硬件产生中断,宣告新数据的到来

2. 中断处理程序分配一个缓冲区,并且告诉硬件向哪里传输数据

3. 外围设备将数据写入数据区,完成后,产生另外一个中断

4.处理程序分发新数据,唤醒任何相关进程,然后执行清理工作

 

高效的DMA处理依赖于中断报告。

 

3. 分配DMA缓冲区

使用DMA缓冲区的主要问题是:当大于一页时,它们必须占据连续的物理页,因为设备使用ISAPCI系统总线传输数据,而这两种方式使用的都是物理地址。

使用get_free_pasges可以分配多大几M字节的内存(MAX_ORDER11),但是对于较大数量(即使是远小于128KB)的请求,通常会失败,这是因为系统内存充满了内存碎片。

解决方法之一就是在引导时分配内存,或者为缓冲区保留顶部物理内存。

例子:在系统引导时,向内核传递参数“mem=value”的方法保留顶部的RAM。比如系统有256内存,参数“mem=255M”,使内核不能使用顶部的1M字节。随后,模块可以使用下面代码获得该内存的访问权:

dmabuf=ioremap(0XFF00000/**255M/, 0X100000/*1M/*);

解决方法之二是使用GPF_NOFAIL分配标志为缓冲区分配内存,但是该方法为内存管理子系统带来了相当大的压力。

解决方法之三十设备支持分散/聚集I/O,这可以将缓冲区分配成多个小块,设备会很好地处理它们。

 

4. 通用DMA

DMA操作最终会分配缓冲区,并将总线地址传递给设备。内核提高了一个与总线——体系结构无关的DMA层。强烈建议在编写驱动程序时,为DMA操作使用该层。使用这些函数的头文件是<linux/dmamapping.h>

int dma_set_mask(struct device *dev, u64 mask);

该掩码显示该设备能寻址能力对应的位。比如说,设备受限于24位寻址,则mask应该是0x0FFFFFF

5. DMA映射

IOMMU在设备可访问的地址范围内规划了物理内存,使得物理上分散的缓冲区对设备来说成连续的。对IOMMU的运用需要使用到通用DMA层,而vir_to_bus函数不能完成这个任务。但是,x86平台没有对IOMMU的支持。

解决之道就是建立回弹缓冲区,然后,必要时会将数据写入或者读出回弹缓冲区。缺点是降低系统性能。

根据DMA缓冲区期望保留的时间长短,PCI代码区分两种类型的DMA映射:

一是一致性DMA映射,存在于驱动程序生命周期中,一致性映射的缓冲区必须可同时被CPU和外围设备访问。一致性映射必须保存在一致性缓存中。建立和使用一致性映射的开销是很大的。

二是流式DMA映射,内核开发者建议尽量使用流式映射,原因:一是在支持映射寄存器的系统中,每个DMA映射使用总线上的一个或多个映射寄存器,而一致性映射生命周期很长,长时间占用这些这些寄存器,甚至在不使用他们的时候也不释放所有权;二是在一些硬件中,流式映射可以被优化,但优化的方法对一致性映射无效。

6. 建立一致性映射

驱动程序可调用pci_alloc_consistent函数建立一致性映射:

void *dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *dma_handle, int falg);

该函数处理了缓冲区的分配和映射,前两个参数是device结构和所需的缓冲区的大小。函数在两处返回DMA映射的结果:函数的返回值是缓冲区的内核虚拟地址,可以被驱动程序使用;而与其相关的总线地址保存在dma_handle中。

当不再需要缓冲区时,调用下函数:

void dma_free_conherent(struct device *dev, size_t size, void *vaddr, dma_addr_t *dma_handle);

7. DMA

DMA池是一个生成小型,一致性DMA映射的机制。调用dma_alloc_coherent函数获得的映射,可能其最小大小为单个页。如果设备需要的DMA区域比这还小,就是用DMA池。在<linux/dmapool.h>中定义了DMA池函数:

struct dma_pool *dma_pool_create(const char *name, struct device *dev, size_t size, size_t align, size_t allocation);

void dma_pool_destroy(struct dma_pool *pool);

nameDMA池的名字,devdevice结构,size是从该池中分配的缓冲区的大小,align是该池分配操作所必须遵守的硬件对齐原则(用字节表示),如果allocation不为零,表示内存边界不能超越allocation。比如说传入的allocation4K,表示从该池分配的缓冲区不能跨越4KB的界限。

在销毁之前必须向DMA池返回所有分配的内存。

void * dma_pool_alloc(sturct dma_pool *pool, int mem_flags, dma_addr_t *handle);

void dma_pool_free(struct dma_pool *pool, void *addr, dma_addr_t addr);

8. 建立流式DMA映射       

在某些体系结构中,流式映射也能够拥有多个不连续的页和多个“分散/聚集”缓冲区。建立流式映射时,必须告诉内核数据流动的方向。

DMA_TO_DEVICE

DEVICE_TO_DMA

如果数据被发送到设备,使用DMA_TO_DEVICE;而如果数据被发送到CPU,则使用DEVICE_TO_DMA

DMA_BIDIRECTTONAL

如果数据可双向移动,则使用该值

DMA_NONE

该符号只是出于调试目的。

当只有一个缓冲区要被传输的时候,使用下函数映射它:

dma_addr_t dma_map_single(struct device *dev, void *buffer, size_t size, enum dma_data_direction direction);

返回值是总线地址,可以把它传递给设备;如果执行错误,返回NULL

当传输完毕后,使用下函数删除映射:

void dma_unmap_single(struct device *dev, dma_addr_t dma_addr, size_t size, enum dma-data_direction direction);

使用流式DMA的原则:

一是缓冲区只能用于这样的传送,即其传送方向匹配与映射时给定的方向值;

二是一旦缓冲区被映射,它将属于设备,不是处理器。直到缓冲区被撤销映射前,驱动程序不能以任何方式访问其中的内容。只用当dma_unmap_single函数被调用后,显示刷新处理器缓存中的数据,驱动程序才能安全访问其中的内容。

三是在DMA出于活动期间内,不能撤销对缓冲区的映射,否则会严重破坏系统的稳定性。             

如果要映射的缓冲区位于设备不能访问的内存区段(高端内存),怎么办?一些体系结构只产生一个错误,但是其他一些系统结构件创建一个回弹缓冲区。回弹缓冲区就是内存中的独立区域,它可被设备访问。如果使用DMA_TO_DEVICE标志映射缓冲区,并且需要使用回弹缓冲区,则在最初缓冲区中的内容作为映射操作的一部分被拷贝。很明显,在拷贝后,最初缓冲区内容的改变对设备不可见。同样DEVICE_TO_DMA回弹缓冲区被dma_unmap_single函数拷贝回最初的缓冲区中,也就是说,直到拷贝操作完成,来自设备的数据才可用。

有时候,驱动程序需要不经过撤销映射就访问流式DMA缓冲区的内容,为此内核提供了如下调用:

void dma_sync_single_for_cpu(struct device *dev, dma_handle_t bus_addr, size_t size, enum dma_data_directction direction);

应该在处理器访问流式DMA缓冲区前调用该函数。一旦调用了该函数,处理器将“拥有”DMA缓冲区,并可根据需要对它进行访问。然后在设备访问缓冲区前,应该调用下面的函数将所有权交还给设备:

void dma_sync_single_for_device(struct device *dev, dma_handle_t bus_addr, size_t size, enum dma_data_direction direction);

再次强调,处理器在调用该函数后,不能再访问DMA缓冲区了。

### DMA直接内存访问)在嵌入式系统中的工作原理 直接内存访问DMA)是一种数据传输机制,允许外设内存之间或内存内存之间直接传输数据,而无需CPU的干预。这种机制显著降低了CPU的负载,提高了系统的整体效率。在嵌入式系统中,DMA通常用于高速数据传输,例如ADC采样、SPI通信、USB传输等场景。 DMA的工作原理基于通道的概念。每个DMA通道可以配置为特定的数据传输方向(如从外设到内存或从内存到外设),并且支持优先级和传输模式的配置。DMA控制器在检测到外设的请求信号后,会接管总线控制权,完成数据的传输,传输完成后将控制权交还给CPU。 ### STM32F407 微控制器中的 DMA 工作原理及使用介绍 STM32F407微控制器内置了两个DMA控制器(DMA1和DMA2),总共支持16个通道(DMA1有7个通道,DMA2有8个通道)。每个通道都可以独立配置,支持多种传输模式和优先级设置。DMA控制器支持外设到内存、内存到外设、内存到内存等多种传输方式,并且可以多个外设(如SPI、I2C、ADC、DAC等)配合使用,实现高效的数据传输。 DMA的高效数据搬运能力对于实现高质量的音频信号处理至关重要。例如,在音频采集或播放系统中,DMA可以将ADC采集的数据直接传输到内存缓冲区,或者将内存中的音频数据传输到DAC模块,而无需CPU频繁中断处理,从而提升系统性能[^3]。 #### DMA 配置步骤 在STM32F407中使用DMA通常需要以下几个步骤: 1. **使能DMA时钟**:通过RCC寄存器使能DMA控制器的时钟。 2. **配置DMA通道**:设置DMA通道的源地址、目标地址、传输方向、数据大小、传输模式等参数。 3. **关联外设DMA**:将外设的DMA请求信号DMA通道连接。 4. **使能DMA传输**:启动DMA通道并开始数据传输。 #### 示例代码:使用DMA传输ADC数据 以下是一个使用DMA将ADC多通道采样值传输到内存的示例代码: ```c #include "stm32f4xx.h" #define ADC_BUFFER_SIZE 4 uint16_t adc_buffer[ADC_BUFFER_SIZE]; void DMA_ADC_Init(void) { // 使能ADC1和DMA2时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE); // 配置ADC ADC_InitTypeDef ADC_InitStruct; ADC_InitStruct.ADC_Resolution = ADC_Resolution_12b; ADC_InitStruct.ADC_ScanConvMode = ENABLE; ADC_InitStruct.ADC_ContinuousConvMode = ENABLE; ADC_InitStruct.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None; ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStruct.ADC_NbrOfConversion = ADC_BUFFER_SIZE; ADC_Init(ADC1, &ADC_InitStruct); // 配置ADC通道 ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_3Cycles); ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_3Cycles); ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_3Cycles); ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_3Cycles); // 使能DMA请求 ADC_DMACmd(ADC1, ENABLE); // 配置DMA通道 DMA_InitTypeDef DMA_InitStruct; DMA_InitStruct.DMA_Channel = DMA_Channel_0; DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; DMA_InitStruct.DMA_Memory0Addr = (uint32_t)&adc_buffer; DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralToMemory; DMA_InitStruct.DMA_BufferSize = ADC_BUFFER_SIZE; DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; DMA_InitStruct.DMA_Mode = DMA_Mode_Circular; DMA_InitStruct.DMA_Priority = DMA_Priority_High; DMA_InitStruct.DMA_FIFOMode = DMA_FIFOMode_Disable; DMA_InitStruct.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull; DMA_InitStruct.DMA_MemoryBurst = DMA_MemoryBurst_Single; DMA_InitStruct.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; DMA_Init(DMA2_Stream0, &DMA_InitStruct); // 使能DMA传输 DMA_Cmd(DMA2_Stream0, ENABLE); // 启动ADC转换 ADC_Cmd(ADC1, ENABLE); ADC_SoftwareStartConv(ADC1); } int main(void) { DMA_ADC_Init(); while (1) { // 主循环中可处理adc_buffer中的数据 } } ``` ###
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值