目录
4.2.2. scatterlist streaming DMA
4.2.2. streaming DMA sync cache
6.12 dmaengine_prep_dma_cyclic
6.13 dmaengine_prep_interleaved_dma
1. DMA子系统概述
DMA子系统为不同的DMA控制器提供统一的注册接口,为DMA使用者提供屏蔽DMA控制器实现细节的统一接口。
2. DMA子系统软件框架
DMA子系统由四大部分构成:
1. dmaengine: 实现DMA子系统的DMA设备注册,为DMA使用者提供屏蔽DMA控制器实现细节的统一接口。
2. virt-dma:为DMA子系统提供虚拟DMA channel的支持
3. of-dma:为DMA子系统提供DMA 设备树的支持
4. dma driver:为DMA控制器的实现
2.1 DMA子系统内部实现
以全志平台为例。
DMA内部主要数据结构已经流出如上图所示。
3. DMA 地址
CPU CPU Bus
Virtual Physical Address
Address Address Space
Space Space
+-------+ +------+ +------+
| | |MMIO | Offset | |
| | Virtual |Space | applied | |
C +-------+ --------> B +------+ ----------> +------+ A
| | mapping | | by host | |
+-----+ | | | | bridge | | +--------+
| | | | +------+ | | | |
| CPU | | | | RAM | | | | Device |
| | | | | | | | | |
+-----+ +-------+ +------+ +------+ +--------+
| | Virtual |Buffer| Mapping | |
X +-------+ --------> Y +------+ <---------- +------+ Z
| | mapping | RAM | by IOMMU
| | | |
| | | |
+-------+ +------+
DMA中会涉及到几类型的地址:
1. 虚拟地址:内核通常使用的地址就是虚拟地址,大部分是由kmalloc、vmalloc以及类似的接口申请并返回。
2. 物理地址:DRAM空间的地址
3. 总线地址:系统mapping出来的地址空间
如上图中
A-->B-->C的过程表示设备寄存器的访问过程
X-->Y-->Z的过程表示DMA访问设备FIFO的过程
在某些系统(U-boot)DMA直接访问物理地址,但是在许多系统,是通过 IOMMU来实现物理地址的转换(Z-->Y)。
例如:通过提供虚拟地址X给dma_map_single(),dma_map_single()通过该IOMMU映射以及返回DMA 可以访问的总线地址Z,以及IOMMU映射Z到Y(系统RAM,例如DRAM空间地址)
Tips:
-
1). 在Linux系统中虚拟地址通常存储在void *,物理地址通常存储在phys_addr_t或者resource_size_t。
2). 在某些平台上总线地址=物理地址,但通常不这么做,IOMMUs和主机网桥可以在物理地址和总线地址之间生成任意映射
3). 总线地址A通过内部桥转换为CPU物理地址B,Ioremap()实现物理地址B到虚拟地址C的映射,然后可以ioread32(C)来访问总线地址A
4).DMA地址应该使用dma_addr_t类型
4. DMA类型
这里说的DMA类型是从DMA地址的角度出发的,分为两类:
1. Coherent(consistent) DMA(一致性DMA)
2. Streaming DMA(流式DMA)
4.1. Coherent DMA
Coherent DMA在访问DDR的时候不经过cpu cache。
Coherent DMA确保设备和CPU可以并行访问数据,并在不进行任何显式软件刷新的情况下看到彼此进行的更新。
Tips:
- 1). 通常在驱动初始化时候map,在卸载或者结束的时候unmap
- 2). 如果要确保设备按顺序看到你的配置可以使用内存屏障来保证内存排序:
- e.g.
-
desc->word0 = address; wmb(); desc->word1 = DESC_VALID;
3). 在某些平台上,驱动程序可能需要刷新CPU写缓冲区(跟CPU架构有关,ARM cortex_a53 没有write buffer, ARMv9有write buffer),其方式与刷新PCI桥中的写缓冲区的方式大致相同
-
4.1.1 Coherent DMA使用
- 1). 如果申请和映射的Coherent DMA域大于PAGE_SIZE(e.g. 4KB)使用如下接口:
-
dma_alloc_coherent。
-
e.g.
-
/*a. alloc coherent */ dma_addr_t dma_handle; cpu_addr = dma_alloc_coherent(dev, size, &dma_handle, gfp); @cpu_addr:CPU角度访问的虚拟地址,按PAGE_SIZE对齐,意味着大于等于申请大小 @dma_handle: DMA角度访问的物理地址,按PAGE_SIZE对齐,意味着大于等于申请大小 /*b. free coherent*/ dma_free_coherent(dev, size, cpu_addr, dma_handle);
注意:
-
alloc出来的coherent dma域按PAGE_SIZE对齐。
-
2). 如果申请和映射的Coherent DMA域小于PAGE_SIZE(e.g. 4KB)使用如下接口:
-
/*dma pool*/ /*a.Create a dma_pool*/ struct dma_pool *pool = dma_pool_create(name, dev, size, align, boundary); @size:必须对齐2 /*b.Allocate memory from a DMA pool*/ cpu_addr = dma_pool_alloc(pool, flags, &dma_handle) @ flag:GFP_KERNEL /*c.Free memory*/ dma_pool_free(pool, cpu_addr, dma_handle); /*Destroy a dma_pool*/ dma_pool_destroy(pool) 调用之前先释放所有在dma pool中申请的内存
-
-
4.2. Streaming DMA
-
Streaming DMA在访问DDR的时候经cpu 过cache。
-
Streaming DMA流式DMA通常在某一次的传输中使用,使用之后取消。
-
4.2.1. Streaming DMA使用
有两类型的streaming DMA映射:single、scatterlist
1). Single streaming DMA
不使用HIGHMEM:
/*a. map a single region*/
dma_handle = dma_map_single(dev, addr, size, direction)
@dma_handle:DMA操作的物理地址
@addr:需要map的虚拟地址
/*b. unmap, DMA传输结束调用*/
dma_unmap_single(dev, dma_handle, size, direction);
-
使用HIGHMEM:
-
dma_handle = dma_map_page(dev, page, offset, size, direction); @offset: offset within the given page (unit by Byte) dma_unmap_page(dev, dma_handle, size, direction);
4.2.2. scatterlist streaming DMA
-
/*map*/ count = dma_map_sg(dev, sglist, nents, direction) /*unmap*/ dma_unmap_sg(dev, sglist, nents, direction); @nents:与传入dma_map_sg()的nents相同,而不是返回值count。
4.2.2. streaming DMA sync cache
-
如果需要使用streaming DMA region多次,并且在这之间需要访问数据,为了确保CPU和device看到的都是一致的,且是最新的,应该按照如下弄:(DMA传输后,CPU看到的是最新的)
-
读:
第一:首先调用dma_map_{single,sg}()第二:在每个DMA传输后dma_sync_single_for_cpu(dev, dma_handle, size, direction);
或者dma_sync_sg_for_cpu(dev, sglist, nents, direction);
-
如果需要让DMA在CPU访问数据后得到的是最新的数据,可以按如下操作:
在CPU访问完数据后调用dma_sync_single_for_device(dev, dma_handle, size, direction);
或者dma_sync_sg_for_device(dev, sglist, nents, direction);
-
5. DMA方向
1). DMA_BIDIRECTIONAL
如果不清楚数据的方向就用DMA_BIDIRECTIONAL,需要具体的平台支持,会影响性能。
读写内存之前都要sync一下。
2). DMA_TO_DEVICE
数据从内存写道设备,在软件最后一次操作内存之后,在DMA传给设备之前,必须sync一下。
3). DMA_FROM_DEVICE
数据从设备读到内存,在软件读内存之前必须sync一下。
4). DMA_NONE
用于DEBUG
Tips:
streaming 映射需要指定方向,consistent映射已经隐式地设置了方向为DMA_BIDIRECTIONAL
-
6. DMA API
-
6.1. dma_alloc_coherent
一致性内存是指设备或处理器的写操作可以被处理器或设备立即读取而不必担心缓存效果的内存。不过,您可能需要确保在告知之前刷新处理器的写入缓冲区(ARM cortex-a53没有写入缓冲区,ARMv9 有)。
-
void *dma_alloc_coherent(struct device *dev, size_t size,dma_addr_t *dma_handle, gfp_t flag)
void *dma_zalloc_coherent(struct device *dev, size_t size, dma_addr_t *dma_handle, gfp_t flag) // 类似dma_alloc_coherent().返回空间清零。
Void dma_free_coherent(struct device *dev, size_t size, void *cpu_addr,dma_addr_t dma_handle) // 释放申请的coherent内存
-
6.2. dma_pool*
-
/* 用于需要申请很多小(小于PAGE_SIZE的大小)coherent内存*/
struct dma_pool *dma_pool_create(const char *name, struct device *dev, size_t size, size_t align, size_t alloc);
@name:用于辨别这个dma pool
@aling:2的倍数对齐
/*从dma pool中申请coherent内存*/
void *dma_pool_alloc(struct dma_pool *pool, gfp_t gfp_flags, dma_addr_t *dma_handle);// 返回CPU的地址
@dma_handle:接收DMA地址
void *dma_pool_zalloc(struct dma_pool *pool, gfp_t mem_flags,dma_addr_t *handle)//类似dma_pool_alloc()
/*把alloc的内存交回给dma pool*/
void dma_pool_free(struct dma_pool *pool, void *vaddr,dma_addr_t addr);
/*释放dma pool*/
void dma_pool_destroy(struct dma_pool *pool);
6.3 dma_set_mask*
Int dma_set_mask_and_coherent(struct device *dev, u64 mask) //检查并更新streaming & coherent DMA的地址位宽
@0 return 表示成功
Int dma_set_mask(struct device *dev, u64 mask)//检查并更新streaming DMA的地址位宽
@0 return 表示成功
Int dma_set_coherent_mask(struct device *dev, u64 mask) // 检查并更新coherent DMA的地址位宽
@0 return 表示成功
-
if (dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32))) { dev_warn(dev, "mydev: No suitable DMA available\n"); goto ignore_this_device; } /*有时候设置64位掩码可能失败,不是因为64位寻址不支持,而是因为内核觉得32位更有效。这个时候对于64位设备可以这么设置:*/ if (!dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64))) using_dac = 1; consistent_using_dac = 1; } else if (!dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32))) { using_dac = 0; consistent_using_dac = 0; } else { dev_warn(dev, "mydev: No suitable DMA available\n"); goto ignore_this_device; }
6.4 dma_map_single
dma_addr_t dma_map_single(struct device *dev, void *cpu_addr, size_t size, enum dma_data_direction direction)
void dma_unmap_single(struct device *dev, dma_addr_t dma_addr, size_t size, enum dma_data_direction direction)
//用此接口必须保证虚拟地址在物理地址上也是连续的,例如kmalloc出来的虚拟地址
@direction:
DMA_NONE,
DMA_TO_DEVICE,
DMA_FROM_DEVICE,
DMA_BIDIRECTIONAL
-
struct device *dev = &my_dev->dev; dma_addr_t dma_handle; void *addr = buffer->ptr; size_t size = buffer->len; dma_handle = dma_map_single(dev, addr, size, direction); if (dma_mapping_error(dev, dma_handle)) { goto map_error_handling; } /*调用dma_mapping_error()来检查dma_map_single()是否失败,但是并不是所有的DMA实现都支持dma_mapping_error()*/
6.6 dma_map_sg
int i, count = dma_map_sg(dev, sglist, nents, direction); struct scatterlist *sg; for_each_sg(sglist, sg, count, i) { hw_address[i] = sg_dma_address(sg); hw_len[i] = sg_dma_len(sg); } @nents:sglist 的entries个数 ... dma_unmap_sg(dev, sglist, nents, direction); @nents:与传入dma_map_sg()的nents相同,而不是返回值count。
6.7 dma_map_page
dma_addr_t dma_map_page(struct device *dev, struct page *page, unsigned long offset, size_t size,enum dma_data_direction direction)
void dma_unmap_page(struct device *dev, dma_addr_t dma_address, size_t size, enum dma_data_direction direction)
int dma_get_cache_alignment(void)
//返回处理器缓存对齐。这是映射内存或进行部分刷新时必须遵守的绝对最小对齐*和*宽度
void dma_cache_sync(struct device *dev, void *vaddr, size_t size, enum dma_data_direction direction)
/sync部分内存,且这个内存是由dma_alloc_noncoherent()申请的。
struct device *dev = &my_dev->dev; dma_addr_t dma_handle; struct page *page = buffer->page; unsigned long offset = buffer->offset; size_t size = buffer->len; dma_handle = dma_map_page(dev, page, offset, size, direction); if (dma_mapping_error(dev, dma_handle)) { /* * reduce current DMA mapping usage, * delay and try again later or * reset driver. */ goto map_error_handling; } ... dma_unmap_page(dev, dma_handle, size, direction);
6.8 dma_sync_single_for*
void dma_sync_single_for_cpu(dev, dma_handle, size, direction)// 在DMA把数据从device搬到DDR后,在cpu 访问DDR之前调用,目的是为了让cpu看到最新的数据
void dma_sync_single_for_device(dev, dma_handle, size, direction)// 在DMA把数据从DDR搬到device之前调用,目的是为了让device看到最新的数据
my_card_setup_receive_buffer(struct my_card *cp, char *buffer, int len) { dma_addr_t mapping; mapping = dma_map_single(cp->dev, buffer, len, DMA_FROM_DEVICE); if (dma_mapping_error(cp->dev, mapping)) { /* * reduce current DMA mapping usage, * delay and try again later or * reset driver. */ goto map_error_handling; } cp->rx_buf = buffer; cp->rx_len = len; cp->rx_dma = mapping; give_rx_buf_to_card(cp); } ... my_card_interrupt_handler(int irq, void *devid, struct pt_regs *regs) { struct my_card *cp = devid; ... if (read_card_status(cp) == RX_BUF_TRANSFERRED) { struct my_card_header *hp; /* Examine the header to see if we wish * to accept the data. But synchronize * the DMA transfer with the CPU first * so that we see updated contents. */ dma_sync_single_for_cpu(&cp->dev, cp->rx_dma, cp->rx_len,DMA_FROM_DEVICE); /* Now it is safe to examine the buffer. */ hp = (struct my_card_header *) cp->rx_buf; if (header_is_ok(hp)) { dma_unmap_single(&cp->dev, cp->rx_dma, cp->rx_len, DMA_FROM_DEVICE); pass_to_upper_layers(cp->rx_buf); make_and_setup_new_rx_buf(cp); } else { /* CPU should not write to * DMA_FROM_DEVICE-mapped area, * so dma_sync_single_for_device() is * not needed here. It would be required * for DMA_BIDIRECTIONAL mapping if * the memory was modified. */ give_rx_buf_to_card(cp); } }
6.9 dma_request_chan
查找并返回与该设备关联的名为name的DMA通道,这种关联是通过设备树来实现的。
通过该接口申请的channel直到使用dma_release_channel()释放为止都是独占该通道的。
struct dma_chan *dma_request_chan(struct device *dev, const char *name)
6.10 dmaengine_slave_config
传递指定的信息给DMA驱动,这些信息大部分已经集成在struct dma_slave_config中。
如果需要传递更多的信息(DMA控制器需要更多的信息),可以将struct dma_slave_config内嵌到控制器的指定结构体中,这种就可以传递更多参数了。
int dmaengine_slave_config(struct dma_chan *chan, struct dma_slave_config *config)
2.11 dmaengine_prep_slave_sg
散列表的形式传输数据,在调用dmaengine_prep_slave_sg()之前需要使用散列表的映射,并且必须在DMA操作完成之前不能释放。如果需要同步,请调用dma_sync_*_for_*()。
struct dma_async_tx_descriptor *dmaengine_prep_slave_sg(
struct dma_chan *chan, struct scatterlist *sgl,
unsigned int sg_len, enum dma_data_direction direction,
unsigned long flags);
nr_sg = dma_map_sg(chan->device->dev, sgl, sg_len); if (nr_sg == 0) /* error */ desc = dmaengine_prep_slave_sg(chan, sgl, nr_sg, direction, flags); 注意: 一旦获取到描述符desc ,就必须尽快submit,因为有些DMA engine会在获取desc后持有spinlock。
6.12 dmaengine_prep_dma_cyclic
循环模式传输数据
struct dma_async_tx_descriptor *dmaengine_prep_dma_cyclic(
struct dma_chan *chan, dma_addr_t buf_addr, size_t buf_len,
size_t period_len, enum dma_data_direction direction);
6.13 dmaengine_prep_interleaved_dma
交叉模式传输数据
struct dma_async_tx_descriptor *dmaengine_prep_interleaved_dma(
struct dma_chan *chan, struct dma_interleaved_template *xt,
unsigned long flags);
-
6.14 dmaengine_submit
把描述符添加到挂起的队列中来。
dma_cookie_t dmaengine_submit(struct dma_async_tx_descriptor *desc)
6.15 dma_async_issue_pending
激活挂起队列中的事务,一旦一个事务完成,队列中的下一个事务就开启,如果设置了回调,还会调用回调通知完成。
void dma_async_issue_pending(struct dma_chan *chan)
-
6.16 dmaengine_terminate*
终止传输,会丢失还未传输的数据
int dmaengine_terminate_sync(struct dma_chan *chan)
int dmaengine_terminate_async(struct dma_chan *chan)
int dmaengine_terminate_all(struct dma_chan *chan) /* DEPRECATED */6.17 dmaengine_pause
-
暂停channel传输,不会导致丢失数据
int dmaengine_pause(struct dma_chan *chan)
6.18 dmaengine_resume
唤醒当前暂停的DMA通道
int dmaengine_resume(struct dma_chan *chan)
6.19 dma_async_is_tx_complete
检查channel的状态,例如传输是否完成。
enum dma_status dma_async_is_tx_complete(struct dma_chan *chan,
dma_cookie_t cookie, dma_cookie_t *last, dma_cookie_t *used)
6.20 dmaengine_synchronize
Synchronize the termination of the DMA channel to the current context.
该函数应该在调用dmaengine_terminate_async()后使用,这个函数将等待传输完成并且调用回调完成。
void dmaengine_synchronize(struct dma_chan *chan)
7. DMA engine usage
- The slave DMA usage consists of following steps:
1.Allocate a DMA slave channel
2.Set slave and controller specific parameters
3.Get a descriptor for transaction
4.Submit the transaction
5.Issue pending requests and wait for callback notification
1.Allocate a DMA slave channel
struct dma_chan *dma_request_chan(struct device *dev, const char *name)
查找并返回与该设备关联的名为name的DMA通道,这种关联是通过设备树来实现的。
通过该接口申请的channel直到使用dma_release_channel()释放为止都是独占该通道的。
2.Set slave and controller specific parameters
int dmaengine_slave_config(struct dma_chan *chan, struct dma_slave_config *config)
传递指定的信息给DMA驱动,这些信息大部分已经集成在struct dma_slave_config中。
如果需要传递更多的信息(DMA控制器需要更多的信息),可以将struct dma_slave_config内嵌到控制器的指定结构体中,这种就可以传递更多参数了。
3.Get a descriptor for transaction
DMA Engine支持多种从机传输模式。
1). single传输
struct dma_async_tx_descriptor *dmaengine_prep_slave_single(
struct dma_chan *chan, dma_addr_t buf, size_t len, enum dma_transfer_direction dir, unsigned long flags)
2). scatterlist传输
struct dma_async_tx_descriptor *dmaengine_prep_slave_sg(
struct dma_chan *chan, struct scatterlist *sgl,
unsigned int sg_len, enum dma_data_direction direction,
unsigned long flags);
如果使用散列表的形式,在调用dmaengine_prep_slave_sg()之前需要使用散列表的映射,并且必须在DMA操作完成之前不能释放。如果需要同步,请调用dma_sync_*_for_*()。通常的操作如下:
nr_sg = dma_map_sg(chan->device->dev, sgl, sg_len);
if (nr_sg == 0)
/* error */
desc = dmaengine_prep_slave_sg(chan, sgl, nr_sg, direction, flags);
注意:
一旦获取到描述符desc ,就必须尽快submit,因为有些DMA engine会在获取desc后持有spinlock。
3). dma_cyclic传输
struct dma_async_tx_descriptor *dmaengine_prep_dma_cyclic(
struct dma_chan *chan, dma_addr_t buf_addr, size_t buf_len,
size_t period_len, enum dma_data_direction direction);
4). interleaved_dma传输
struct dma_async_tx_descriptor *dmaengine_prep_interleaved_dma(
struct dma_chan *chan, struct dma_interleaved_template *xt,
unsigned long flags);
4. Submit the transaction
一旦desc一获取以及回调信息添加了,就必须将它到达DMA engine驱动队列中来。
dma_cookie_t dmaengine_submit(struct dma_async_tx_descriptor *desc)
dmaengine_submit()不会开启一个DMA的操作,它仅仅是把它添加到挂起的队列中来。
5. Issue pending DMA requests and wait for callback notification
void dma_async_issue_pending(struct dma_chan *chan);
通过dma_async_issue_pending()来激活挂起队列中的事务,一旦一个事务完成,队列中的下一个事务就开启,如果设置了回调,还会调用回调通知完成。