DMAC (direct memory access controller)


DMA

DMA(Direct memory access) 特性允许在CPU参与的情况下外设访问DDR。如常见的ARM SOCCPU core通过AXI master,经常NOC(Network on a chipe)路由到DDR AXI SLAVE接口,实现DDR访问。在SOC没有DMA特性下,如果DDR需要和外设之间搬移数据,只能通过CPU 指令,这样会耗费大量的CPU时间。如果有DMACPU配置好source addressdestination address,还有搬移数据的长度,DMAC(DMA controller)会在CPU不参与下搬移数据,CPU可以做其他工作,在完成后有中断通知到CPU,进行相应的处理。有些DMAC还支持link listt模式,一次搬移多个不连续的DDR请求。

通常情况下SOC下的负载IPSD/SDIO/eMMC controller[chapter 26], USB Controller[chapter 31]都自带专有的DMAC。其他相对简单的IP如串口、SPI共用内部的通用DMAC[chapter 19](general purpose direct memory access)DMACCPU访问DDR大概架构如下。其中CPUDMAC都有AXI master访问DDR,同时CPU可以通过APB总线配置DMAC相关寄存器。

简单CPUDMAC访问DDR架构图



通过图1可以看到CPU访问DDRDMAC有一点不同时,路径上经过CacheCache是为了加速DDR访问的一种缓存机制。因为有cache存在,CPUDMAC看到的同一个DDR地址空间可能不一致。有两种情况导致不一致,第一种是CPU写,DDR读。第二种是DDR写, CPU读。第一种的不一致例子如DDR 0x80000000地址处值为0CPU往该地址写入值为2,因为cacheWrite-back特性同时Cache可用,2只存在Cache中不会flushDDR上,所以DMACDDR看到的值还是0。第二种不一致和类似。对于这种SOC需要CPU相关指令clean cache , invalid cacheARM V7架构有一个Accelerator Coherency Port (ACP)接口,DMACCPU都通过Cache访问DDR达到缓存一致的效果,避免CPU操作cacheACP位于图2中虚线箭头,为了区别ACP请求和普通DDR请求,经过ACP口的地址可以和普通的DDR地址不一致,NOC通过地址路由到相关的接口。这个地址的偏移ARM平台可以通过设备数的dma-ranges指定。如stm32mp151设备数的相关配置

mlahb {

compatible = "simple-bus";

#address-cells = <1>;

#size-cells = <1>;

dma-ranges = <0x00000000 0x38000000 0x10000>,

<0x10000000 0x10000000 0x60000>,

<0x30000000 0x30000000 0x60000>;

}

带有ACP接口的DMAC

因为ARM PTE页表policy可以设置成Strongly-ordered(no-cache)特性,CPU访问绕过Cache,因此就没有数据不一致的问题。图3中虚线为CPU绕过Cache直接访问DDR,不存在数据不一致问题。

3 stronger-order policyDMACCPU访问路径

IOMMU

从上面的介绍可以看到,CPUDMAC会同时访问内存,如果DMAC软件BUG造成越界写DDR,从CPU角度很难发现问题。对于不支持link list模式的DMAC搬移大量的数据时,可能由于系统内存碎片导致申请不到一大段连续内存,导致请求失败。鉴于这些原因在DMACDDR之间引入IOMMUARM也实现的IOMMU功能叫做SMMU。图IOMMUDMAC架构图。

带有IOMMUDMAC架构图

IOMMUCPU侧的MMU类似机制,对于DDR请求会根据页表做相应的映射,再请求到页表对应DDR的物理地址。所以设定好DMAC IOMMU页表,可以固定DDR请求的物理地址,避免访问非法DDR物理地址。对于非法的范围,出发相应的异常中断。

Linux DMA架构

LinuxDMA的架构如下图5..对于内存申请,IOMMU映射,Cache的操作通过inlclude/linux/dma-mapping.h相关接口。include/linux/dmaengine.h为使用通用DMAC相关接口。对于自带DMACIP,只需要关注include/linux/dma-mapping.h相关接口。

5 linux DMA架构

通用的DMAC会通过DMA Engine导出统一的API。通用的处理流程如下。具体可参考dmates.c

Allocate a DMA slave channel

Set slave and controller specific parameters

Get a descriptor for transaction

Submit the transaction

Issue pending requests and wait for callback notification

其中申请给DMA使用的内存有两种类型,一种Consistent DMA mappings ,另外一种是Streaming DMA mappings Consistent DMA mappings也称作coherent DMA mapping,也就是驱动先申请一段stronger order内存,绕过cache达到数据一致性。Streaming DMA mappings使用的DDR normal policycache的内存,所以需要clean/invalid cache操作达到数据一致性。

Consistent DMA mapping API

这类接口适合驱动初始化时候分配好,后续的DMA相关操作固定使用申请的内存。申请接口为dma_alloc_coherent,释放接口为dma_free_coherent



static inline void *dma_alloc_coherent(struct device *dev, size_t size,

dma_addr_t *dma_handle, gfp_t gfp)

返回值为CPU 范围的地址, dma_handleDMAC操作地址。主要的流程如下

dma_alloc_coherent ->dma_mmap_attrs->dev->dma_ops->alloc

dma_alloc_coherent

->dma_mmap_attrs

-->dev->dma_ops->alloc



其中dev->dma_ops是在platform bus绑定platform device 解析其device tree设置的,相应的流程如下

driver_probe_device

->platform_dma_configure

->of_dma_configure

--->of_dma_get_range //dma-ranges map

--->of_dma_is_coherent // coherent

--->of_iommu_configure_device // get iommu_ops

--->arch_setup_dma_ops

---->arm_setup_iommu_dma_ops

----->arm_iommu_create_mapping

----->arm_get_iommu_dma_map_ops // iommu_coherent_ops

其中arm_get_iommu_dma_map_ops会根据device tree中时候配置coherent,决定返回值。这里说的coherent也就是这个DMAC支持ACP,对于带有cacheDDR也不需要操作cache。这里分析不带coherent配置相关接口,也就是iommu_ops。继续上述的dev->dma_ops->alloc接口arm_iommu_alloc_attrs

arm_iommu_alloc_attrs

->__arm_iommu_alloc_attrs

-->__iommu_alloc_buffer

-->__iommu_create_mapping //创建DMA address DDR地址映射,配置IOMMU 页表

-->dma_common_pages_remap //通过vmap接口创建stronger order内存映射

dma_common_pages_remap中的pte页表的配置通过__get_dma_pgprot。可以看到pte的配置是L_PTE_MT_UNCACHED

static inline pgprot_t __get_dma_pgprot(unsigned long attrs, pgprot_t prot)

{

prot = (attrs & DMA_ATTR_WRITE_COMBINE) ?

pgprot_writecombine(prot) :

pgprot_dmacoherent(prot);

return prot;

}

#define pgprot_dmacoherent(prot) \

__pgprot_modify(prot, L_PTE_MT_MASK, L_PTE_MT_UNCACHED | L_PTE_XN)

#endif

Streaming DMA mapping API

Streaming DMA mapping适用于内存已分配好,操作相应的cacheIOMMU mapDMAC使用。例如用户态发送TCP/IP数据,数据包是协议栈通过alloc_skb分配的normal policy的带cache的内存,再发起DMA操作前需要操作cache

dma_map_single 对内存分配操作cache,再进行IOMMU map操作返回DMA address

static inline dma_addr_t dma_map_single_attrs(struct device *dev, void *ptr,

size_t size, enum dma_data_direction dir, unsigned long attrs)

dma_map_single_attrs

->dev->dma_ops->alloc //Consistent DMA mapping probe driver设定的

arm_iommu_map_page

-->__dma_page_cpu_to_dev //通过dir确定clean/invalid cache

-->arm_coherent_iommu_map_page //构建IOMMU映射,返回DMA address