Arm linux dma mapping操作

Arm linux dma mapping

概述

由于处理器存在cache,cache和内存中数据可能不一致,所以驱动在使用dma在内存和device之间搬移数据前后需要cpu对cache和内存中数据进行同步。有些dma寻址能力有限,比如只能寻址内存低128m,但数据在内存的1G地址处,这时需要进行数据转移。

基本概念

cpu读取数据时先查询l1 cache,如果没有再向l2 cache
查询,如果没有在向内存查询,然后把数据从ddr 内存搬移到l2 cache和l1
cache。Cpu写数据,改变了l1 cache中内容,这时l1 cache可能把数据写会到ddr
内存,也可能过段时间再写。两个l1 cache和l2
cache之间数据同步,arm中按照moesi协议同步,这里不进行介绍。

基本概念:

PoU:在单核中在某一存储层级上,指令cache,数据cache和TLB在某一点上能够看到一致内容或相同copy,称为Pou;或者在an
Inner Shareable shareability
domain中,指令cache,数据cache和TLB在某一点上能够看到一致内容或相同copy,称为Pou;

PoC:系统中所有agents(系统中master,或者observer,例如,cpu,dma)看到的memory
一致点, 称为PoC.

例如,图中CPU core0和CPU core1为一组cluster0,CPU core2和CPU
core3为一组cluster1,

L2 cache为PoU,DDR memory为PoC。

Clean:把cache中指定地址内容写会到PoU或PoC。

Invalidate:
使cache中指定地址内容无效,如果cache中修改没有写回内存,那么这次修改会丢失。

Clean and invalidate:clean指令后执行invalidate指令。

写回(write back):修改了cache中内容后,并不把更新立即写到内存,把修改的cache
line标记为dirty。但执行clean操作时,把cache修改内容写的内存,或者,替换cache
line时,把cache修改内容写的内存。

写直通(write through):修改了cache中内容后,立即写到内存。
在这里插入图片描述
流式 DMA 和一致性 DMA

在一致性 DMA 映射中,它采用的是系统预留的一段 DMA 内存用于 DMA 操作,这一段内核在系统启动阶段就已经预留完毕,比如 arm64 平台会在 dts 文件中写明系统预留的 DMA 内存段位于何处,并且会被标志为用于 dma 一致性内存申请,如果你有关注 DMA 的一致性映射操作 API 就会发现,一致性 DMA 不会去使用别的地方申请的内存,它都是通过dma_alloc_coherent自我申请内存,然后驱动自己填充数据最后被提交给 DMA 控制器。

流式 DMA 中,它可以是随意的内存交给 DMA 进行处理,不需要从系统预留的 DMA 位置进行内存申请,任何普通的 kmalloc 申请的内存都能交给 DMA 控制器进行操作。

二者是如何做到缓存一致性的:

一致性 DMA ,在 DMA 内存申请的过程中,首先进行一个 ioremap_nocache 的映射,然后调用函数 dma_cache_wback_inv 保证缓存已经刷新到位之后,后面使用这一段内存时不存在一二级缓存;

流式 DMA ,不能直接禁止缓存,因为流式 DMA 可以使用系统中的任意地址范围的地址,CPU 总不能将系统所有的地址空间都禁止缓存,这不科学,那么为了实现缓存一致性,流式 DMA 需要不断的对缓存进行失效操作,告诉 CPU 这一段缓存是不可信的,必须从内存中重新获取。一致性 DMA 就是直接将缓存禁止,而流式 DMA 则是将缓存失效刷新。

Cache操作

写数据到device

  1. cpu执行clean指令,把cache中写到内存

  2. cpu配置dma,dma搬移内存中数据到device

从device读数据

  1. dma搬移device数据到内存

  2. cpu执行 invalidate指令,使cache中内容无效;

  3. cpu处理数据,由于之前设置了cache无效,所以会从内存读取数据到cache

swio技术

swio技术,当dma寻址能力小于cpu分配的物理地址时,swio技术会从低端地址,比如0-64m中分配一块物理地址,建立低端物理地址和cpu分配的物理地址映射,并且后续dma使用低端dma地址进行数据搬移,如果是dma从物理内存搬移数据到设备
(DMA TO DEVICE
),那么cpu先把数据从cpu分配物理地址复制到低端物理地址,后续dma从低端物理地址搬移到设备。如果是dma从设备搬移数据到物理内存(DMA
FROM DEVICE
),那么dma从设备搬移到低端物理地址,然后通知cpu,cpu从低端物理地址复制数据到原高端物理地址,
但如果dma寻址能力够,就不需要这一步。

代码分析

arm64_dma_init

\arch\arm64\mm\dma-mapping.c 中设置dma_ops

static int \__init arm64_dma_init(void)

{

int ret = 0;

ret \|= swiotlb_late_init();

ret \|= atomic_pool_init();

return ret;

}

arch_initcall(arm64_dma_init);

static int \__init swiotlb_late_init(void)

{

size_t swiotlb_size = min(SZ_64M, MAX_ORDER_NR_PAGES \<\< PAGE_SHIFT);

/\*

\* These must be registered before of_platform_populate().

\*/

bus_register_notifier(&platform_bus_type, \&platform_bus_nb);
注册notifier,当有bus注册到系统时,会回调这个bus_nb函数

bus_register_notifier(&amba_bustype, \&amba_bus_nb);

dma_ops = \&noncoherent_swiotlb_dma_ops; 设置了noncoherent ops

return swiotlb_late_init_with_default_size(swiotlb_size);

}

static int dma_bus_notifier(struct notifier_block \*nb,

unsigned long event, void \*_dev)

{

struct device \*dev = \_dev;

if (event != BUS_NOTIFY_ADD_DEVICE)

return NOTIFY_DONE;

如果dts中指定了bus dma是dma-coherent,那么设置ops为coherent_swiotlb_dma_ops。

if (of_property_read_bool(dev-\>of_node, "dma-coherent"))

set_dma_ops(dev, \&coherent_swiotlb_dma_ops);

return NOTIFY_OK;

}

看一下noncoherent dma ops

struct dma_map_ops noncoherent_swiotlb_dma_ops = {

.alloc = \__dma_alloc_noncoherent,

.free = \__dma_free_noncoherent,

.mmap = \__swiotlb_mmap_noncoherent,

.map_page = \__swiotlb_map_page,

.unmap_page = \__swiotlb_unmap_page,

.map_sg = \__swiotlb_map_sg_attrs,

.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,

};

EXPORT_SYMBOL(noncoherent_swiotlb_dma_ops);

Cpu会调用dma_map_single

dma_map_single

例如:usb驱动收发数据

Cpu会调用dma_map_single进行clean或invalid

int usb_gadget_map_request(struct usb_gadget \*gadget,

struct usb_request \*req, int is_in)

{

req-\>dma = dma_map_single(&gadget-\>dev, req-\>buf, req-\>length,

is_in ? DMA_TO_DEVICE : DMA_FROM_DEVICE);

DMA_TO_DEVICE从内存搬移数据到device,DMA_FROM_DEVICE从device搬移数据到内存

return 0;

}

dma_map_single—》dma_map_single_attrs—》dma_map_single—》ops-\>map_page-》__swiotlb_map_page

static dma_addr_t \__swiotlb_map_page(struct device \*dev, struct page \*page,

unsigned long offset, size_t size,

enum dma_data_direction dir,

struct dma_attrs \*attrs)

{

dma_addr_t dev_addr;

dev_addr = swiotlb_map_page(dev, page, offset, size, dir, attrs);
swio技术,这里不再介绍,认为dma寻址能力足够;

这里先进行了dma地址和cpu内存物理地址,cpu虚拟地址转换

这里dma地址和cpu内存物理地址相同。这个由soc总线设计决定。

Cpu看到的内存地址和dma看到的内存地址可能不同。

\__dma_map_area(phys_to_virt(dma_to_phys(dev, dev_addr)), size, dir);

return dev_addr;

}

\\arch\\arm64\\mm\\cache.S

/\*

\* \__dma_map_area(start, size, dir)

\* - start - kernel virtual start address

\* - size - size of region

\* - dir - DMA direction

\*/

ENTRY(__dma_map_area)

add x1, x1, x0

cmp w2, \#DMA_FROM_DEVICE 来自设备,就是从设备搬移数据到ap ddr中。

b.eq \__dma_inv_range
走这里,所以要先使cache中数据无效,后续dma搬移后,从ddr中直接读取到cache中。

b \__dma_clean_range 接着把cache中内容刷到内存中,
clean。确保之前cache修改内容,写回内存。
实际这里有没有必要执行,可以根据场景进行优化。

\__dma_inv_range: invalid就是使cache中内容无效,下次使用时需要从内存中重新读取

dcache_line_size x2, x3

sub x3, x2, \#1

tst x1, x3 // end cache line aligned?

bic x1, x1, x3

b.eq 1f

dc civac, x1 // clean & invalidate D / U line这里检查结束地址是否cache line
aligned,如果不对齐,需要把cacheline数据写到内存。因为cache
是按line处理,否则其它(非本次dma搬移地址)地址数据也变为无效,导致数据无法写会内存。

1: tst x0, x3 // start cache line aligned?

bic x0, x0, x3

b.eq 2f

dc civac, x0 // clean & invalidate D / U line 同上,起时地址

b 3f

2: dc ivac, x0 // invalidate D / U line 使无效

3: add x0, x0, x2

cmp x0, x1

b.lo 2b

dsb sy

ret

ENDPROC(__inval_cache_range)

ENDPROC(__dma_inv_range)

/\*

\* \__dma_clean_range(start, end)

\* - start - virtual start address of region

\* - end - virtual end address of region

\*/

\__dma_clean_range:

dcache_line_size x2, x3

sub x3, x2, \#1

bic x0, x0, x3

1: dc cvac, x0 // clean D / U line

add x0, x0, x2

cmp x0, x1

b.lo 1b

dsb sy

ret

ENDPROC(__dma_clean_range)

上面用到的invalidate指令,都是到PoC的。civac ,ivac

DC CIVAC, Xt Clean and invalidate by virtual address to Point of Coherency

DC IVAC, Xt Invalidate by virtual address to Point of Coherency

dma_unmap_single

void usb_gadget_unmap_request(struct usb_gadget \*gadget,

struct usb_request \*req, int is_in)

{

if (req-\>length == 0)

return;

if (req-\>num_mapped_sgs) {

dma_unmap_sg(&gadget-\>dev, req-\>sg, req-\>num_mapped_sgs,

is_in ? DMA_TO_DEVICE : DMA_FROM_DEVICE);

req-\>num_mapped_sgs = 0;

} else {

dma_unmap_single(&gadget-\>dev, req-\>dma, req-\>length,

is_in ? DMA_TO_DEVICE : DMA_FROM_DEVICE);

}

}

上面map_page,然后dma搬移,现在进行unmap_page

static void \__swiotlb_unmap_page(struct device \*dev, dma_addr_t dev_addr,

size_t size, enum dma_data_direction dir,

struct dma_attrs \*attrs)

{

\__dma_unmap_area(phys_to_virt(dma_to_phys(dev, dev_addr)), size, dir);
先进行cache操作

swiotlb_unmap_page(dev, dev_addr, size, dir, attrs); swio技术

}

/\*

\* \__dma_unmap_area(start, size, dir)

\* - start - kernel virtual start address

\* - size - size of region

\* - dir - DMA direction

\*/

ENTRY(__dma_unmap_area)

add x1, x1, x0

cmp w2, \#DMA_TO_DEVICE 如果是写数据到device,这里不需要再操作,

b.ne \__dma_inv_range 从device搬移到内存走这里,再次invalid
cache,防止dma搬移期间,cpu操作过这段地址或相邻地址,会导致这块地址被读到cache中。 特别是dma搬移的地址不是cache
line对齐场景。

ret

ENDPROC(__dma_unmap_area)

设置nocache

armv8参考手册
D7.2.63 MAIR_EL1, Memory Attribute Indirection Register (EL1)
mair_el1寄存器设置内存的属性,0-7个属性可配置
n的值有页表中描述符的attrindx[2:0]决定,例如linux 页表中pte表项
在这里插入图片描述
在这里插入图片描述
页表描述符:
在这里插入图片描述在这里插入图片描述
在这里插入图片描述

内核启动阶段,proc.s中设置了mair寄出器各个属性值,如下:
\arch\arm64\mm\proc.S

/*
 * Memory region attributes for LPAE:
 *
 *   n = AttrIndx[2:0]
 *			n	MAIR
 *   DEVICE_nGnRnE	000	00000000     n为0时,属性
 *   DEVICE_nGnRE	001	00000100     
 *   DEVICE_GRE		010	00001100
 *   NORMAL_NC		011	01000100      n为3时,属性表示0100 0100,表示no cache,两个0100
 *   NORMAL		100	11111111
 */
ldr	x5, =MAIR(0x00, MT_DEVICE_nGnRnE) | \
	     MAIR(0x04, MT_DEVICE_nGnRE) | \
	     MAIR(0x0c, MT_DEVICE_GRE) | \
	     MAIR(0x44, MT_NORMAL_NC) | \
	     MAIR(0xff, MT_NORMAL)
msr	mair_el1, x5

/*

  • Memory types available.
    */
    #define MT_DEVICE_nGnRnE 0
    #define MT_DEVICE_nGnRE 1
    #define MT_DEVICE_GRE 2
    #define MT_NORMAL_NC 3
    #define MT_NORMAL 4
    #define PROT_NORMAL_NC (PROT_DEFAULT | PTE_PXN | PTE_UXN | PTE_ATTRINDX(MT_NORMAL_NC))
    PROT_NORMAL_NC 对应 4,也就是n=3的属性,为 nochache
static void *__dma_alloc_noncoherent(struct device *dev, size_t size,
				     dma_addr_t *dma_handle, gfp_t flags,
				     struct dma_attrs *attrs)
{
	struct page *page;
	void *ptr, *coherent_ptr;

	size = PAGE_ALIGN(size);

	if (!(flags & __GFP_WAIT)) {
		struct page *page = NULL;
		void *addr = __alloc_from_pool(size, &page);

		if (addr)
			*dma_handle = phys_to_dma(dev, page_to_phys(page));

		return addr;

	}

	ptr = __dma_alloc_coherent(dev, size, dma_handle, flags, attrs);
	if (!ptr)
		goto no_mem;

	/* remove any dirty cache lines on the kernel alias */
	__dma_flush_range(ptr, ptr + size);

	/* create a coherent mapping */
	page = virt_to_page(ptr);
	coherent_ptr = dma_common_contiguous_remap(page, size, VM_USERMAP,
				__get_dma_pgprot(attrs,
					__pgprot(PROT_NORMAL_NC), false),    //设置内存页表pte属性attrindx[2:0],对应pte中bit[4:2],为3,也就是对应mair_el1中的nocache
					NULL);
	if (!coherent_ptr)
		goto no_map;

	return coherent_ptr;

no_map:
	__dma_free_coherent(dev, size, ptr, *dma_handle, attrs);
no_mem:
	*dma_handle = ~0;
	return NULL;
}

总结:页表pte中设置attrindx[2:0],对应pte中bit[4:2],为mair_el1寄存器的n值,索引,实际页属性配置在mair_el1寄存器中

  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值