前言
对于长期从事嵌入式的开发人员来说,DMA是一个在实际中优化设备而老生常谈的问题,我们可以将它比喻为一个小机器人,长期重复与某一个动作的傻瓜式机器人,当我们设置好了出发添加,这个小机器人就会自动去执行相关的业务而不需要CPU的参与,从而大大的减轻了cpu的负载压力。
一、对于使用DMA控制器有什么内存上限制?
dma的内存要求内存上是连续的一片虚拟内存空间,
1、我们不要使用vmalloc以及kmalloc这些通过get_free_page()函数来触发系统异常来分配在内存上可能是不连续的内存地址,而使用dma_map_接口样式的dma接口函数进行dma的内存分配以及相关的操作。
2、使用全局数据来作为dma的内存地址是完全可行的。
例如、
dma_addr_t dma_handle;
cpu_addr = dma_alloc_coherent(dev, size, &dma_handle, gfp);//分配一片内存用于DMA
dma_free_coherent(dev, size, cpu_addr, dma_handle);//使用申请的dma内存
二、DMA的寻址限制
不同的硬件平台对于dma的寻址是有限制的,比如系统平台是32位,而DMA控制器的智能驱动低24位,那么高于16M的内存空间dma是无法寻址的。
一旦确定了dma的寻址限制后,我们可以使用如下函数对接口进行限制、
int dma_set_mask_and_coherent(struct device *dev, u64 mask);
更具dma的特征,dma buffer可分为两种,一种为临时使用,使用完毕就丢弃,另外一种是长时间占据,临时使用我们需要考虑dma的一致性问题,使用如下函数都可以设置dma掩码来解决的一致性问题。
int dma_set_mask(struct device *dev, u64 mask);//设置临时使用的
int dma_set_coherent_mask(struct device *dev, u64 mask);//对于长期使用
三、DMA mapping
1、consistent DMA mapping(一致性dma映射)
它有如下特点、
1.1、在dma控制器初始化时候已经map,在shutdown发送时候自动unmap
1.2、不存在cache一致性问题,当buffer发生更新时候,dma控制器会自动捕获到。
一般使用场景、
(1)、网卡驱动,SCSI等相关的硬件设计可以通过dma控制器可以通过buffer的某些描述符捕获到buffer发生了更新。
2、streaming dma mapping(流式DMA映射)
所谓流式,就是在使用的时候才进行mapping动作,一旦传输完成,立即unmap
这个就很多了,比如spi,i2c,uart等,但是无论使用哪一个,都需要内存进行对其。
四、使用问题
1、dma_map_single
使用dma_map_single进行内存分配使用,
1、需要调用dma_mapping_error进行错误检查
2、dma_unmap_single进行内存释放
2、多个dma map时候可以使用scatterlist进行分配
scatterlist可以将多个不连续的map buffer在内存空间上进行连续分配。
nt 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);
}
//scatterlist分配的buffer进行释放
dma_unmap_sg(dev, sglist, nents, direction);
3、sync操作
如果你需要多次访问同一个streaming DMA buffer,并且在DMA传输之间读写DMA Buffer上的数据,这时候你需要小心进行DMA buffer的sync操作,以便CPU和设备(DMA controller)可以看到最新的、正确的数据。
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;
//HW已经完成了传输,在cpu访问buffer之前,cpu需要先sync一下,以便看到最新的数据。
dma_sync_single_for_cpu(&cp->dev, cp->rx_dma,
cp->rx_len,
DMA_FROM_DEVICE);
//sync之后就可以安全的读dma 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 {
give_rx_buf_to_card(cp);
}
}
}
注意、
1、在很多的平台上,dma_unmap_{single,page}()其实什么也没有做,是空函数