宋寶華:關於linux內存管理中DMA ZONE和dma_alloc_coherent若干誤解的澄清【轉】

轉自:https://blog.csdn.net/21cnbao/article/details/79133658

本文已首先在Linuxer公衆號(ID: LinuxDev)發表,先轉回我的blog也發表。轉載請註明出處。

1.DMA ZONE的大小是16MB?

這個答案在32位X86計算機的條件下是成立的,但是在其他的絕大多數情況下都不成立。

首先我們要理解DMA ZONE產生的歷史原因是什麼。DMA可以直接在內存和外設之間進行數據搬移,對於內存的存取來講,它和CPU一樣,是一個訪問master,可以直接訪問內存。

DMA ZONE產生的本質原因是:不一定所有的DMA都可以訪問到所有的內存,這本質上是硬件的設計限制。

在32位X86計算機的條件下,ISA實際只可以訪問16MB以下的內存。那麼ISA上面假設有個網卡,要DMA,超過16MB以上的內存,它根本就訪問不到。所以Linux內核乾脆簡單一點,把16MB砍一刀,這一刀以下的內存單獨管理。如果ISA的驅動要申請DMA buffer,你帶一個GFP_DMA標記來表明你想從這個區域申請,我保證申請的內存你是可以訪問的。

 DMA ZONE的大小,以及DMA ZONE要不要存在,都取決於你實際的硬件是什麼。比如我在CSR工作的時候,CSR的primaII芯片,儘管除SD MMC控制器以外的所有的DMA都可以訪問整個4GB內存,但MMC控制器的DMA只能訪問256MB,我們就把primaII對應Linux的DMA ZONE設爲了256MB,詳見內核: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 <[email protected]> */

        .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

 

不過CSR這個公司由於早前已經被Q記收購,已經不再存在,一起幻滅的,還有當年掛在汽車前窗上的導航儀。這不禁讓我想起我們當年在ADI arch/blackfin裏面寫的代碼,也漸漸快幾乎沒有人用了一樣。

一代人的芳華已逝,面目全非,重逢雖然談笑如故,可不難看出歲月給每個人帶來的改變。原諒我不願讓你們看到我們老去的樣子,就讓代碼,留住我們芬芳的年華吧........

下面我們架空歷史,假設有一個如下的芯片,裏面有5個DMA,A、B、C都可以訪問所有內存,D只能訪問32MB,而E只能訪問64MB,你覺得Linux的設計者會把DMA ZONE設置爲多大?當然是32MB,因爲如果設置爲64MB,D從DMA ZONE申請的內存就可能位於32MB-64MB之間,申請了它也訪問不了。

 

 

 

由於現如今絕大多少的SoC都很牛逼,似乎DMA都沒有什麼缺陷了,根本就不太可能給我們機會指定DMA ZONE大小裝逼了,那個這個ZONE就不太需要存在了。反正任何DMA在任何地方申請的內存,這個DMA都可以存取到。

2.DMA ZONE的內存只能做DMA嗎?

DMA ZONE的內存做什麼都可以。DMA ZONE的作用是讓有缺陷的DMA對應的外設驅動申請DMA buffer的時候從這個區域申請而已,但是它不是專有的。其他所有人的內存(包括應用程序和內核)也可以來自這個區域。

3.dma_alloc_coherent()申請的內存來自DMA ZONE?

dma_alloc_coherent()申請的內存來自於哪裏,不是因爲它的名字前面帶了個dma_就來自DMA ZONE的,本質上取決於對應的DMA硬件是誰。看代碼:

 

  1. static void *__dma_alloc(struct device *dev, size_t size, dma_addr_t *handle,
  2. gfp_t gfp, pgprot_t prot, bool is_coherent, const void *caller)
  3. {
  4. u64 mask = get_coherent_dma_mask(dev);
  5. if (mask < 0xffffffffULL)
  6. gfp |= GFP_DMA;
  7. }

對於primaII而言,絕大多少的外設的dma_coherent_mask都設置爲0XffffffffULL(4GB內存全覆蓋),但是SD那個則設置爲256MB-1對應的數字。這樣當primaII的SD驅動調用dma_alloc_coherent()的時候,GFP_DMA標記被設置,以指揮內核從DMA ZONE申請內存。但是,其他的外設,mask覆蓋了整個4GB,調用dma_alloc_coherent()獲得的內存就不需要一定是來自DMA ZONE。

 

4.dma_alloc_coherent()申請的內存是非cache的嗎?

要解答這個問題,首先要理解什麼叫cache coherent。還是繼續看這個DMA的圖,我們假設MEM裏面有一塊紅色的區域,並且CPU讀過它,於是紅色區域也進CACHE:



但是,假設現在DMA把外設的一個白色搬移到了內存原本紅色的位置:

這個時候,內存雖然白了,CPU讀到的卻還是紅色,因爲CACHE命中了,這就出現了cache的不coherent。
當然,如果是CPU寫數據到內存,它也只是先寫進cache(不一定進了內存),這個時候如果做一個內存到外設的DMA操作,外設可能就得到錯誤的內存裏面的老數據。
所以cache coherent的最簡單方法,自然是讓CPU訪問DMA buffer的時候也不帶cache。事實上,缺省情況下,dma_alloc_coherent()申請的內存缺省是進行uncache配置的。
但是,由於現代SoC特別強,這樣有一些SoC裏面可以用硬件做CPU和外設的cache coherence,如圖中的cache coherent interconnect:

這些SoC的廠商就可以把內核的通用實現overwrite掉,變成dma_alloc_coherent()申請的內存也是可以帶cache的。這部分還是讓大牛Arnd Bergmann童鞋來解釋:

 

來自: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.

 

當我grep內核源代碼的時候,我發現部分SoC確實是這樣實現的:

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()申請的內存一定是物理連續的嗎?

絕大多數的SoC目前都支持和使用CMA技術,並且多數情況下,DMA coherent APIs以CMA區域爲申請的後端,這個時候,dma alloc coherent本質上用__alloc_from_contiguous()從CMA區域獲取內存,申請出來的內存顯然是物理連續的。這一點,在設備樹dts裏面就可以輕鬆配置,要麼配置一個自己特定的cma區域,要麼從“linux,cma-default”指定的缺省的CMA池子裏面取內存:

 

  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)的話,DMA完全可以訪問非連續的內存,並且把物理上不連續的內存,用IOMMU進行重新映射爲I/O virtual address (IOVA):

所以dma_alloc_coherent()這個API只是一個前端的界面,它的內存究竟從哪裏來,究竟要不要連續,帶不帶cache,都完全是因人而異的。

 

最後總結一句,千萬不要被教科書和各種網上的資料懵逼了雙眼,你一定要真正自己探索和搞清楚事情的本源。

今天看了《芳華》這部電影,感慨良多,遂作此文。

更多精華文章請掃描下方二維碼關注Linux閱碼場

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章