本文轉(zhuǎn)載自微信公眾號(hào)[Linux閱碼場(chǎng)],原創(chuàng):宋寶華
創(chuàng)作目的
互聯(lián)網(wǎng)、Linux內(nèi)核書(shū)籍上充滿(mǎn)了各種關(guān)于Linux DMA ZONE和dma_alloc_coherent救拉、dma_map_single等的各種講解,由于很多童鞋缺乏自身獨(dú)立的思考邻悬,人云亦云撞蚕,對(duì)這些概念形成了很多錯(cuò)誤的理解。本文的目的在于徹底澄清這些誤解顶吮。
當(dāng)你發(fā)現(xiàn)本文內(nèi)容與baidu到的內(nèi)容不一致的時(shí)候社牲,以本文內(nèi)容為準(zhǔn)
1.DMA ZONE的大小是16MB?
這個(gè)答案在32位X86計(jì)算機(jī)的條件下是成立的悴了,但是在其他的絕大多數(shù)情況下都不成立膳沽。
首先我們要理解DMA ZONE產(chǎn)生的歷史原因是什么。DMA可以直接在內(nèi)存和外設(shè)之間進(jìn)行數(shù)據(jù)搬移让禀,對(duì)于內(nèi)存的存取來(lái)講挑社,它和CPU一樣,是一個(gè)訪問(wèn)master巡揍,可以直接訪問(wèn)內(nèi)存痛阻。
DMA ZONE產(chǎn)生的本質(zhì)原因是:不一定所有的DMA都可以訪問(wèn)到所有的內(nèi)存,這本質(zhì)上是硬件的設(shè)計(jì)限制腮敌。
在32位X86計(jì)算機(jī)的條件下,ISA實(shí)際只可以訪問(wèn)16MB以下的內(nèi)存阱当。那么ISA上面假設(shè)有個(gè)網(wǎng)卡俏扩,要DMA,超過(guò)16MB以上的內(nèi)存弊添,它根本就訪問(wèn)不到录淡。所以Linux內(nèi)核干脆簡(jiǎn)單一點(diǎn),把16MB砍一刀油坝,這一刀以下的內(nèi)存單獨(dú)管理嫉戚。如果ISA的驅(qū)動(dòng)要申請(qǐng)DMA buffer,你帶一個(gè)GFP_DMA標(biāo)記來(lái)表明你想從這個(gè)區(qū)域申請(qǐng)澈圈,我保證申請(qǐng)的內(nèi)存你是可以訪問(wèn)的彬檀。
DMA ZONE的大小,以及DMA ZONE要不要存在瞬女,都取決于你實(shí)際的硬件是什么窍帝。比如我在CSR工作的時(shí)候,CSR的primaII芯片诽偷,盡管除SD MMC控制器以外的所有的DMA都可以訪問(wèn)整個(gè)4GB內(nèi)存坤学,但MMC控制器的DMA只能訪問(wèn)256MB,我們就把primaII對(duì)應(yīng)Linux的DMA ZONE設(shè)為了256MB报慕,詳見(jiàn)內(nèi)核:arch/arm/mach-prima2/common.c
#ifdef CONFIG_ARCH_PRIMA2
static const char *const prima2_dt_match[] __initconst = {
"sirf,prima2",
NULL
};
DT_MACHINE_START(PRIMA2_DT, "Generic PRIMA2 (Flattened Device Tree)")
/* Maintainer: Barry Song <baohua.song@csr.com> */
.l2c_aux_val = 0,
.l2c_aux_mask = ~0,
.dma_zone_size = SZ_256M,
.init_late = sirfsoc_init_late,
.dt_compat = prima2_dt_match,
MACHINE_END
#endif
不過(guò)CSR這個(gè)公司由于早前已經(jīng)被Q記收購(gòu)拥峦,已經(jīng)不再存在,一起幻滅的卖子,還有當(dāng)年掛在汽車(chē)前窗上的導(dǎo)航儀略号。這不禁讓我想起我們當(dāng)年在ADI arch/blackfin里面寫(xiě)的代碼,也漸漸快幾乎沒(méi)有人用了一樣洋闽。
一代人的芳華已逝,面目全非,重逢雖然談笑如故,可不難看出歲月給每個(gè)人帶來(lái)的改變玄柠。原諒我不愿讓你們看到我們老去的樣子,就讓代碼,留住我們芬芳的年華吧........
下面我們架空歷史,假設(shè)有一個(gè)如下的芯片诫舅,里面有5個(gè)DMA羽利,A、B刊懈、C都可以訪問(wèn)所有內(nèi)存这弧,D只能訪問(wèn)32MB,而E只能訪問(wèn)64MB虚汛,你覺(jué)得Linux的設(shè)計(jì)者會(huì)把DMA ZONE設(shè)置為多大匾浪?當(dāng)然是32MB,因?yàn)槿绻O(shè)置為64MB卷哩,D從DMA ZONE申請(qǐng)的內(nèi)存就可能位于32MB-64MB之間蛋辈,申請(qǐng)了它也訪問(wèn)不了。
由于現(xiàn)如今絕大多少的SoC都很牛逼,似乎DMA都沒(méi)有什么缺陷了冷溶,根本就不太可能給我們機(jī)會(huì)指定DMA ZONE大小裝逼了渐白,那個(gè)這個(gè)ZONE就不太需要存在了。反正任何DMA在任何地方申請(qǐng)的內(nèi)存逞频,這個(gè)DMA都可以存取到纯衍。
2.DMA ZONE的內(nèi)存只能做DMA嗎?
DMA ZONE的內(nèi)存做什么都可以苗胀。DMA ZONE的作用是讓有缺陷的DMA對(duì)應(yīng)的外設(shè)驅(qū)動(dòng)申請(qǐng)DMA buffer的時(shí)候從這個(gè)區(qū)域申請(qǐng)而已襟诸,但是它不是專(zhuān)有的。其他所有人的內(nèi)存(包括應(yīng)用程序和內(nèi)核)也可以來(lái)自這個(gè)區(qū)域柒巫。
3.dma_alloc_coherent()申請(qǐng)的內(nèi)存來(lái)自DMA ZONE励堡?
dma_alloc_coherent()申請(qǐng)的內(nèi)存來(lái)自于哪里谷丸,不是因?yàn)樗拿智懊鎺Я藗€(gè)dma_就來(lái)自DMA ZONE的堡掏,本質(zhì)上取決于對(duì)應(yīng)的DMA硬件是誰(shuí)∨偬郏看代碼:
static void *__dma_alloc(struct device *dev, size_t size, dma_addr_t *handle,
gfp_t gfp, pgprot_t prot, bool is_coherent, const void *caller)
{
u64 mask = get_coherent_dma_mask(dev);
…
if (mask < 0xffffffffULL)
gfp |= GFP_DMA;
…
}
對(duì)于primaII而言泉唁,絕大多少的外設(shè)的dma_coherent_mask都設(shè)置為0XffffffffULL(4GB內(nèi)存全覆蓋),但是SD那個(gè)則設(shè)置為256MB-1對(duì)應(yīng)的數(shù)字揩慕。這樣當(dāng)primaII的SD驅(qū)動(dòng)調(diào)用dma_alloc_coherent()的時(shí)候亭畜,GFP_DMA標(biāo)記被設(shè)置,以指揮內(nèi)核從DMA ZONE申請(qǐng)內(nèi)存迎卤。但是拴鸵,其他的外設(shè),mask覆蓋了整個(gè)4GB蜗搔,調(diào)用dma_alloc_coherent()獲得的內(nèi)存就不需要一定是來(lái)自DMA ZONE劲藐。
4.dma_alloc_coherent()申請(qǐng)的內(nèi)存是非cache的嗎?
要解答這個(gè)問(wèn)題樟凄,首先要理解什么叫cache coherent聘芜。還是繼續(xù)看這個(gè)DMA的圖,我們假設(shè)MEM里面有一塊紅色的區(qū)域缝龄,并且CPU讀過(guò)它汰现,于是紅色區(qū)域也進(jìn)CACHE:
但是,假設(shè)現(xiàn)在DMA把外設(shè)的一個(gè)白色搬移到了內(nèi)存原本紅色的位置:
這個(gè)時(shí)候叔壤,內(nèi)存雖然白了瞎饲,CPU讀到的卻還是紅色,因?yàn)镃ACHE命中了炼绘,這就出現(xiàn)了cache的不coherent企软。
當(dāng)然,如果是CPU寫(xiě)數(shù)據(jù)到內(nèi)存饭望,它也只是先寫(xiě)進(jìn)cache(不一定進(jìn)了內(nèi)存)仗哨,這個(gè)時(shí)候如果做一個(gè)內(nèi)存到外設(shè)的DMA操作形庭,外設(shè)可能就得到錯(cuò)誤的內(nèi)存里面的老數(shù)據(jù)。
所以cache coherent的最簡(jiǎn)單方法厌漂,自然是讓CPU訪問(wèn)DMA buffer的時(shí)候也不帶cache萨醒。事實(shí)上,缺省情況下苇倡,dma_alloc_coherent()申請(qǐng)的內(nèi)存缺省是進(jìn)行uncache配置的富纸。
但是,由于現(xiàn)代SoC特別強(qiáng)旨椒,這樣有一些SoC里面可以用硬件做CPU和外設(shè)的cache coherence晓褪,如圖中的cache coherent interconnect:
這些SoC的廠商就可以把內(nèi)核的通用實(shí)現(xiàn)overwrite掉,變成dma_alloc_coherent()申請(qǐng)的內(nèi)存也是可以帶cache的综慎。這部分還是讓大牛Arnd Bergmann童鞋來(lái)解釋?zhuān)?/p>
來(lái)自:https://www.spinics.net/lists/arm-kernel/msg322447.html
Arnd Bergmann:
dma_alloc_coherent() is a wrapper around a device-specific allocator,
based on the dma_map_ops implementation. The default allocator
from arm_dma_ops gives you uncached, buffered memory. It is expected
that the driver uses a barrier (which is implied by readl/writel
but not __raw_readl/__raw_writel or readl_relaxed/writel_relaxed)
to ensure the write buffers are flushed.
If the machine sets arm_coherent_dma_ops rather than arm_dma_ops,
the memory will be cacheable, as it's assumed that the hardware
is set up for cache-coherent DMAs.
當(dāng)我grep內(nèi)核源代碼的時(shí)候涣仿,我發(fā)現(xiàn)部分SoC確實(shí)是這樣實(shí)現(xiàn)的:
baohua@baohua-VirtualBox:~/develop/linux/arch/arm$ git grep arm_coherent_dma_ops
include/asm/dma-mapping.h:extern struct dma_map_ops arm_coherent_dma_ops;
mach-highbank/highbank.c: set_dma_ops(dev, &arm_coherent_dma_ops);
mach-mvebu/coherency.c: set_dma_ops(dev, &arm_coherent_dma_ops);
5.dma_alloc_coherent()申請(qǐng)的內(nèi)存一定是物理連續(xù)的嗎?
絕大多數(shù)的SoC目前都支持和使用CMA技術(shù)示惊,并且多數(shù)情況下好港,DMA coherent APIs以CMA區(qū)域?yàn)樯暾?qǐng)的后端,這個(gè)時(shí)候米罚,dma alloc coherent本質(zhì)上用__alloc_from_contiguous()從CMA區(qū)域獲取內(nèi)存钧汹,申請(qǐng)出來(lái)的內(nèi)存顯然是物理連續(xù)的。這一點(diǎn)录择,在設(shè)備樹(shù)dts里面就可以輕松配置,要么配置一個(gè)自己特定的cma區(qū)域拔莱,要么從“l(fā)inux,cma-default”指定的缺省的CMA池子里面取內(nèi)存:
reserved-memory {
#address-cells = <1>;
#size-cells = <1>;
ranges;
/* global autoconfigured region for contiguous allocations */
linux,cma {
compatible = "shared-dma-pool";
reusable;
size = <0x4000000>;
alignment = <0x2000>;
linux,cma-default;
};
display_reserved: framebuffer@78000000 {
reg = <0x78000000 0x800000>;
};
multimedia_reserved: multimedia@77000000 {
compatible = "acme,multimedia-memory";
reg = <0x77000000 0x4000000>;
};
};
但是,如果IOMMU存在(ARM里面叫SMMU)的話(huà)隘竭,DMA完全可以訪問(wèn)非連續(xù)的內(nèi)存塘秦,并且把物理上不連續(xù)的內(nèi)存,用IOMMU進(jìn)行重新映射為I/O virtual address (IOVA):
所以dma_alloc_coherent()這個(gè)API只是一個(gè)前端的界面货裹,它的內(nèi)存究竟從哪里來(lái)嗤形,究竟要不要連續(xù),帶不帶cache弧圆,都完全是因人而異的赋兵。
6.可以直接在進(jìn)程的虛擬地址空間進(jìn)行DMA操作嗎?
在支持SVA(Shared Virtual Addressing)的場(chǎng)景下搔预,外設(shè)可以和CPU共享相同的虛擬地址霹期,這樣外設(shè)就可以直接共享進(jìn)程的地址空間:
相關(guān)文章:Shared Virtual Addressing for the IOMMU
https://lwn.net/Articles/747230/
當(dāng)然,它要求硬件的三個(gè)支持(這里面每個(gè)要求都是必須的拯田,我認(rèn)為最最最重要的是IOMMU的page table和CPU MMU的page table格式是兼容的):
The device, buses and the IOMMU must support the following features:
- Multiple address spaces per device, for example using the PCI PASID(Process Address Space ID) extension. The IOMMU driver allocates a PASID and the device uses it in DMA transactions.
- I/O Page Faults (IOPF), for example PCI PRI (Page Request Interface) or Arm SMMU stall. The core mm handles translation faults from the IOMMU.
- MMU and IOMMU implement compatible page table formats.
試想历造,你在用戶(hù)空間的一片內(nèi)存:
*void p = malloc(1MB);
當(dāng)你要對(duì)這片內(nèi)存進(jìn)行壓縮運(yùn)算的時(shí)候,你的硬件里面有個(gè)壓縮加速器,你直接把p這個(gè)地址告訴它吭产,它就可以幫你進(jìn)行壓縮了侣监,這樣的生活是多么的愜意?
我覺(jué)得最最牛逼的是:外設(shè)共享了你寫(xiě)的app的進(jìn)程的地址空間臣淤,外設(shè)直接融入你的應(yīng)用成為它的一部分橄霉,幫應(yīng)用完成部分功能(當(dāng)然最主要是加速功能)。它甚至讓前面的dma_alloc_coherent等待這樣的專(zhuān)門(mén)申請(qǐng)一致性DMA緩沖區(qū)的API都那么的多余邑蒋,你也不需要凡事設(shè)計(jì)到DMA操作的時(shí)候姓蜂,都進(jìn)行內(nèi)核的大量操作。CPU和外設(shè)的絕對(duì)界線被打開(kāi)医吊。
同時(shí)我希望你不要把DMA狹義地理解為內(nèi)存的拷貝钱慢,比如從內(nèi)存里面往網(wǎng)卡里面搬移這樣的事情。本文所述的DMA卿堂,更多地具備廣義DMA的概念束莫,就是外設(shè)可以直接訪問(wèn)內(nèi)存。進(jìn)而我也希望你不要把本文所說(shuō)的外設(shè)想象成狹義的就是USB御吞、網(wǎng)卡麦箍、I2C這種漓藕,本文所述CPU眼里的外設(shè)本身也可能是一個(gè)帶運(yùn)算能力的加速器陶珠、video處理器、GPU等享钞。
如果外設(shè)是個(gè)加速器揍诽,利用SVA,它可以訪問(wèn)到進(jìn)程虛擬地址空間的數(shù)據(jù)進(jìn)行運(yùn)算栗竖,減少了很多不必要的互動(dòng)暑脆,可以極大地提升系統(tǒng)性能。
最后總結(jié)一句狐肢,千萬(wàn)不要被教科書(shū)和各種網(wǎng)上的資料懵逼了雙眼添吗,你一定要真正自己探索和搞清楚事情的本源。