我們知道DMA通常需要訪問連續(xù)的物理內(nèi)存,除非設(shè)備支持iommu,當(dāng)設(shè)備不支持iommu的話可以用以下方式:
- 在內(nèi)核啟動(dòng)是為設(shè)備保留內(nèi)存將MMU內(nèi)嵌到設(shè)備中,如GPU
這里GPU MMU的方式算是個(gè)例外,不在本篇文章討論范圍內(nèi)。
我們知道DMA映射有兩種方式,一種是一致性映射 dma_alloc_coherent,一種是流式映射 dma_map_single (dma_map_sg可以映射多個(gè)dma buffer)。
一致性映射 dma_alloc_coherent
dma_alloc_coherent會(huì)調(diào)用dma_alloc_attrs:
static inline void *dma_alloc_attrs(struct device *dev, size_t size,
dma_addr_t *dma_handle, gfp_t flag,
unsigned long attrs)
{
const struct dma_map_ops *ops = get_dma_ops(dev);
void *cpu_addr;
BUG_ON(!ops);
if (dma_alloc_from_dev_coherent(dev, size, dma_handle, &cpu_addr))
return cpu_addr;
if (!arch_dma_alloc_attrs(&dev, &flag))
return NULL;
if (!ops->alloc)
return NULL;
cpu_addr = ops->alloc(dev, size, dma_handle, flag, attrs);
debug_dma_alloc_coherent(dev, size, *dma_handle, cpu_addr);
return cpu_addr;
}
ops->alloc對(duì)應(yīng)的回調(diào)有兩個(gè)注冊(cè),分別是swiotlb和iommu:
static struct dma_map_ops swiotlb_dma_ops = {
.alloc = __dma_alloc, //dma_alloc_attrs
.free = __dma_free,
.mmap = __swiotlb_mmap,
.get_sgtable = __swiotlb_get_sgtable,
.map_page = __swiotlb_map_page, //dma_map_single
.unmap_page = __swiotlb_unmap_page,
.map_sg = __swiotlb_map_sg_attrs, //dma_map_sg
.unmap_sg = __swiotlb_unmap_sg_attrs,
.sync_single_for_cpu = __swiotlb_sync_single_for_cpu,
.sync_single_for_device = __swiotlb_sync_single_for_device,
.sync_sg_for_cpu = __swiotlb_sync_sg_for_cpu,
.sync_sg_for_device = __swiotlb_sync_sg_for_device,
.dma_supported = __swiotlb_dma_supported,
.mapping_error = __swiotlb_dma_mapping_error,
};
static struct dma_map_ops iommu_dma_ops = {
.alloc = __iommu_alloc_attrs,
.free = __iommu_free_attrs,
.mmap = __iommu_mmap_attrs,
.get_sgtable = __iommu_get_sgtable,
.map_page = __iommu_map_page,
.unmap_page = __iommu_unmap_page,
.map_sg = __iommu_map_sg_attrs,
.unmap_sg = __iommu_unmap_sg_attrs,
.sync_single_for_cpu = __iommu_sync_single_for_cpu,
.sync_single_for_device = __iommu_sync_single_for_device,
.sync_sg_for_cpu = __iommu_sync_sg_for_cpu,
.sync_sg_for_device = __iommu_sync_sg_for_device,
.map_resource = iommu_dma_map_resource,
.unmap_resource = iommu_dma_unmap_resource,
.mapping_error = iommu_dma_mapping_error,
};
非iommu的話即調(diào)用__dma_alloc:
static void *__dma_alloc(struct device *dev, size_t size,
dma_addr_t *dma_handle, gfp_t flags,
unsigned long attrs)
{
......
size = PAGE_ALIGN(size);
if (!coherent && !gfpflags_allow_blocking(flags)) {
......
//coherent_pool
void *addr = __alloc_from_pool(size, &page, flags);
if (addr)
*dma_handle = phys_to_dma(dev, page_to_phys(page));
return addr;
}
//cma or buddy or swiotlb
ptr = __dma_alloc_coherent(dev, size, dma_handle, flags, attrs);
if (!ptr)
goto no_mem;
......
return coherent_ptr;
}
其中__alloc_from_pool用來分配 coherent_pool 的內(nèi)存,__dma_alloc_coherent用來分配 cma 或者 buddy 或者 swiotlb的內(nèi)存。(實(shí)際方案中一般通過memblock的方式劃分coherent pool,cma,swiotlb這三種reserved mem)
其分配流程如下圖所示:
from nxp community by eric chen
相關(guān)解釋:
為了下面流式映射更好的理解,這里再詳細(xì)講下 swiotlb 的分配過程。
__dma_alloc_coherent->swiotlb_alloc_coherent->map_single
static phys_addr_t
map_single(struct device *hwdev, phys_addr_t phys, size_t size,
enum dma_data_direction dir, unsigned long attrs)
{
dma_addr_t start_dma_addr;
if (swiotlb_force == SWIOTLB_NO_FORCE) {
dev_warn_ratelimited(hwdev, "Cannot do DMA to address %pan",
&phys);
return SWIOTLB_MAP_ERROR;
}
start_dma_addr = swiotlb_phys_to_dma(hwdev, io_tlb_start);
return swiotlb_tbl_map_single(hwdev, start_dma_addr, phys, size,
dir, attrs);
}
phys_addr_t swiotlb_tbl_map_single(struct device *hwdev,
dma_addr_t tbl_dma_addr,
phys_addr_t orig_addr, size_t size,
enum dma_data_direction dir,
unsigned long attrs)
{
if (io_tlb_list[index] >= nslots) {
int count = 0;
for (i = index; i < (int) (index + nslots); i++)
io_tlb_list[i] = 0;
for (i = index - 1; (OFFSET(i, IO_TLB_SEGSIZE) != IO_TLB_SEGSIZE - 1) && io_tlb_list[i]; i--)
io_tlb_list[i] = ++count;
tlb_addr = io_tlb_start + (index << IO_TLB_SHIFT);
/*
* Update the indices to avoid searching in the next
* round.
*/
io_tlb_index = ((index + nslots) < io_tlb_nslabs
? (index + nslots) : 0);
goto found;
}
......
return tlb_addr;
}
- 申請(qǐng)bounce buffer并且返回虛擬地址,出去再轉(zhuǎn)為dma地址系統(tǒng)啟動(dòng)的時(shí)候就做好了slots和swiotlb內(nèi)存的映射,這里根據(jù)slot可以返回其地址。
至此,dma_alloc_coherent的分配流程就完成了。我們可以看出雖然申請(qǐng)api都是dma_alloc_coherent函數(shù),但是后臺(tái)的實(shí)現(xiàn)有很多種,并且和是否是dma zone也沒什么必然關(guān)系,本質(zhì)上只是一塊0x0000_0000到0xFFFF_FFFF范圍內(nèi)的連續(xù)內(nèi)存。
流式映射 dma_map_single
因?yàn)镈MA受32位訪問的限制,所以只能訪問0x0000_0000到0xFFFF_FFFF地址空間的內(nèi)存,再加上DMA需要訪問連續(xù)的物理內(nèi)存,故coherent pool,cma,buddy,swiotlb必須保證在0x0000_0000~0xFFFF_FFFF以內(nèi)的連續(xù)物理空間。 這些沒毛病。
但是如果一個(gè)64位系統(tǒng)的話,CPU訪問內(nèi)存完全是可以大于0xFFFF_FFFF范圍的。比如一個(gè)內(nèi)存的基地址是0x80000000,內(nèi)存大小是4G,則內(nèi)存的物理地址范圍是0x8000_0000~0x18000_0000。由于DMA尋址范圍為0x0000_0000~0xFFFF_FFFF,如果CPU把數(shù)據(jù)放在0x10000_0000~0x18000_0000這段空間,DMA就無法訪問了。
怎么解決上面的問題?
此時(shí)swiotlb就登上了歷史舞臺(tái)。
from nxp community by eric chen
swiotlb做的工作如上圖所示,主要通過map_single從swiotlb里找到一塊buffer叫做Bounce Buffer,然后把CPU訪問的Data Buffer與Bounce Buffer映射起來,最后通過swiotlb_bounce把這兩個(gè)buffer中的數(shù)據(jù)做個(gè)同步(memcpy)。
下面我們通過代碼把上面的過程梳理一下。
物理頁映射
dma_map_single->dma_map_single_attrs->(ops->map_page)->__swiotlb_map_page-> swiotlb_map_page-> map_single
dma_addr_t swiotlb_map_page(struct device *dev, struct page *page,
unsigned long offset, size_t size,
enum dma_data_direction dir,
unsigned long attrs)
{
//根據(jù)頁號(hào)獲取物理地址,進(jìn)而獲得DMA地址
phys_addr_t map, phys = page_to_phys(page) + offset;
dma_addr_t dev_addr = phys_to_dma(dev, phys);
BUG_ON(dir == DMA_NONE);
/*
* If the address happens to be in the device's DMA window,
* we can safely return the device addr and not worry about bounce
* buffering it.
*/
//判斷DMA的尋址能力是否能夠覆蓋上一步得到的物理地址,如果能的話,直接返回物理地址,否則采用swiotlb機(jī)制分配內(nèi)存。
if (dma_capable(dev, dev_addr, size) && swiotlb_force != SWIOTLB_FORCE)
return dev_addr;
trace_swiotlb_bounced(dev, dev_addr, size, swiotlb_force);
/* Oh well, have to allocate and map a bounce buffer. */
//用swiotlb機(jī)制分配內(nèi)存
map = map_single(dev, phys, size, dir, attrs);
//判斷調(diào)用swiotlb機(jī)制分配的內(nèi)存物理地址是否在DMA尋址能力范圍內(nèi),如果在的話直接返回,否則直接返回備用地址
if (map == SWIOTLB_MAP_ERROR) {
swiotlb_full(dev, size, dir, 1);
return swiotlb_phys_to_dma(dev, io_tlb_overflow_buffer);
}
dev_addr = swiotlb_phys_to_dma(dev, map);
/* Ensure that the address returned is DMA'ble */
if (dma_capable(dev, dev_addr, size))
return dev_addr;
attrs |= DMA_ATTR_SKIP_CPU_SYNC;
swiotlb_tbl_unmap_single(dev, map, size, dir, attrs);
return swiotlb_phys_to_dma(dev, io_tlb_overflow_buffer);
}
- CPU訪問的內(nèi)存轉(zhuǎn)為DMA地址判斷DMA的尋址能力是否能夠覆蓋上一步得到的地址,如果能的話,直接返回地址,否則采用swiotlb機(jī)制分配內(nèi)存。通過map_single用swiotlb機(jī)制分配內(nèi)存,詳情見上面
至此,CPU對(duì)應(yīng)的Data Buffer和DMA對(duì)應(yīng)的Bounce Buffer就映射起來了
數(shù)據(jù)同步
- dma_sync_single_for_device
dma_sync_single_for_device
_swiotlb_sync_single_for_device
swiotlb_sync_single_for_device
swiotlb_sync_single(..., SYNC_FOR_DEVICE)
swiotlb_tbl_sync_single
swiotlb_bounce(..., DMA_TO_DEVICE)
memcpy(vaddr, buffer + offset, sz) //將數(shù)據(jù)從Data Buffer處拷貝到Bounce Buffer
__dma_map_area
ENTRY(__dma_map_area)
cmp w2, #DMA_FROM_DEVICE
b.eq __dma_inv_area //invalid就是使cache中內(nèi)容無效,下次使用時(shí)需要從內(nèi)存中重新讀取
b __dma_clean_area //把cache中內(nèi)容刷到內(nèi)存中
ENDPIPROC(__dma_map_area)
- dma_sync_single_for_cpu
dma_sync_single_for_cpu
__swiotlb_sync_single_for_cpu
__dma_unmap_area
ENTRY(__dma_unmap_area)
cmp w2, #DMA_TO_DEVICE
b.ne __dma_inv_area //invalid就是使cache中內(nèi)容無效,下次使用時(shí)需要從內(nèi)存中重新讀取
ret
ENDPIPROC(__dma_unmap_area)
swiotlb_sync_single_for_cpu
swiotlb_sync_single(..., SYNC_FOR_CPU)
swiotlb_tbl_sync_single
swiotlb_bounce(..., DMA_FROM_DEVICE)
memcpy(buffer + offset, vaddr, sz) //將數(shù)據(jù)從Bounce Buffer處拷貝到Data Buffer