DMA
DMA(Direct memory access) 特性允許在CPU參與的情況下外設訪問DDR。如常見的ARM SOC,CPU core通過AXI master,經常NOC(Network on a chipe)路由到DDR AXI SLAVE接口,實現DDR訪問。在SOC沒有DMA特性下,如果DDR需要和外設之間搬移數據,只能通過CPU 指令,這樣會耗費大量的CPU時間。如果有DMA,CPU配置好source address、destination address,還有搬移數據的長度,DMAC(DMA controller)會在CPU不參與下搬移數據,CPU可以做其他工作,在完成后有中斷通知到CPU,進行相應的處理。有些DMAC還支持link listt模式,一次搬移多個不連續的DDR請求。
通常情況下SOC下的負載IP如SD/SDIO/eMMC controller[chapter 26], USB Controller[chapter 31]都自帶專有的DMAC。其他相對簡單的IP如串口、SPI共用內部的通用DMAC[chapter 19](general purpose direct memory access)。DMAC和CPU訪問DDR大概架構如下。其中CPU和DMAC都有AXI master訪問DDR,同時CPU可以通過APB總線配置DMAC相關寄存器。
圖1 簡單CPU和DMAC訪問DDR架構圖
通過圖1可以看到CPU訪問DDR和DMAC有一點不同時,路徑上經過Cache。Cache是為了加速DDR訪問的一種緩存機制。因為有cache存在,CPU和DMAC看到的同一個DDR地址空間可能不一致。有兩種情況導致不一致,第一種是CPU寫,DDR讀。第二種是DDR寫, CPU讀。第一種的不一致例子如DDR 0x80000000地址處值為0,CPU往該地址寫入值為2,因為cache為Write-back特性同時Cache可用,2只存在Cache中不會flush到DDR上,所以DMAC從DDR看到的值還是0。第二種不一致和類似。對於這種SOC需要CPU相關指令clean cache , invalid cache。ARM V7架構有一個Accelerator Coherency Port (ACP)接口,DMAC和CPU都通過Cache訪問DDR達到緩存一致的效果,避免CPU操作cache。ACP位於圖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>;
}
圖2 帶有ACP接口的DMAC
因為ARM PTE頁表policy可以設置成Strongly-ordered(no-cache)特性,CPU訪問繞過Cache,因此就沒有數據不一致的問題。圖3中虛線為CPU繞過Cache直接訪問DDR,不存在數據不一致問題。
圖3 stronger-order policy的DMAC和CPU訪問路徑
IOMMU
從上面的介紹可以看到,CPU和DMAC會同時訪問內存,如果DMAC軟件BUG造成越界寫DDR,從CPU角度很難發現問題。對於不支持link list模式的DMAC搬移大量的數據時,可能由於系統內存碎片導致申請不到一大段連續內存,導致請求失敗。鑒於這些原因在DMAC和DDR之間引入IOMMU。ARM也實現的IOMMU功能叫做SMMU。圖4 帶IOMMU的DMAC架構圖。
圖4 帶有IOMMU的DMAC架構圖
IOMMU和CPU側的MMU類似機制,對於DDR請求會根據頁表做相應的映射,再請求到頁表對應DDR的物理地址。所以設定好DMAC IOMMU頁表,可以固定DDR請求的物理地址,避免訪問非法DDR物理地址。對於非法的范圍,出發相應的異常中斷。
Linux DMA架構
Linux下DMA的架構如下圖5..對於內存申請,IOMMU映射,Cache的操作通過inlclude/linux/dma-mapping.h相關接口。include/linux/dmaengine.h為使用通用DMAC相關接口。對於自帶DMAC的IP,只需要關注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 policy帶cache的內存,所以需要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_handle為DMAC操作地址。主要的流程如下
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,對於帶有cache的DDR也不需要操作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適用於內存已分配好,操作相應的cache和IOMMU map給DMAC使用。例如用戶態發送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