概述
swiotlb技术是一种纯软件的地址映射技术,主要为寻址能力受限的DMA提供软件上的地址映射,听起来比较玄乎,实际上其原理非常简单。我们下面先来谈一下该技术提出的背景。
我们假设一个64位系统,其内存的基地址是0x80000000,内存大小是4G,则内存的物理地址范围是0x80000000-0x180000000,同时该系统中某个外设的DMA只能按32 bits地址寻址,即寻址范围为0-0xffffffff,如果恰好非给该外设DMA的内存地址超过了这个范围,该外设DMA实际无法使用该地址。那么怎么办呢?此时swiotlb就登上了舞台!
swiotlb维护了一块低地址的buffer,该buffer的大小可以由bootloader通过swiotlb参数传递给内核,也可以使用默认值,默认值是64M。
如果给DMA分配的物理地址(这里记做phyaddr1)超过了其寻址能力,那么swiotlb技术将从其buffer中分配同样大小的一块空间,给DMA使用,其物理地址记做phyaddr2,swiotlb会维护phyaddr1和phyaddr2的映射关系。
这里有两个问题比较关键:
第一个问题是:如何确保swiotlb维护的buffer在低地址呢?
这个问题很好回答,只要尽可能早地分配该buffer就可以了,只要分配的足够早,想要哪一块buffer还不是一句话的事!在ARM64平台上在函数mem_init的一开始,就调用swiotlb_init做swiotlb的初始化,swiotlb_init中就会分配所需要的buffer。
第二个问题是:一般来说先分配page,然后映射其物理地址(这里我们暂时认为是物理地址,其实是DMA地址),如果在映射物理地址的时候发现物理地址超过DMA的寻址能力,就会偷偷从buffer中申请一块等大小的内存,来替换掉原来内存地址,给DMA使用。但是此时物理page和DMA实际使用的物理地址不一致,也就是说DMA看到内存和CPU看到的内存实际上是两块内存,这两块内存没有任何关系。
以上分析除了最后一句之外,全部是正确的。这两个内存是有关系的,他们的关系就是swiotlb维护了一张io_tlb_orig_addr表,这张表维护了原始物理地址和swiotlb分配物理地址之间的映射关系。当DMA将数据写入swiotlb分配的物理地址后,一般会以中断的方式通知CPU,CPU在查看DMA写到内存的数据之前,需要先做一个sync,该sync操作就会触发swiotlb将数据拷贝到原始物理地址处,这样就保证了CPU能够看到正确的数据,不过显而易见,这种方法效率非常低下。
代码分析
swiotlb初始化
mem_init->swiotlb_init
void __init
swiotlb_init(int verbose)
{
size_t default_size = IO_TLB_DEFAULT_SIZE;
unsigned char *vstart;
unsigned long bytes;
if (!io_tlb_nslabs) {
io_tlb_nslabs = (default_size >> IO_TLB_SHIFT);
io_tlb_nslabs = ALIGN(io_tlb_nslabs, IO_TLB_SEGSIZE);
}
bytes = io_tlb_nslabs << IO_TLB_SHIFT;
/* Get IO TLB memory from the low pages */
vstart = memblock_virt_alloc_low_nopanic(PAGE_ALIGN(bytes), PAGE_SIZE);
if (vstart && !swiotlb_init_with_tbl(vstart, io_tlb_nslabs, verbose))
return;
if (io_tlb_start)
memblock_free_early(io_tlb_start,
PAGE_ALIGN(io_tlb_nslabs << IO_TLB_SHIFT));
pr_warn("Cannot allocate SWIOTLB buffer");
no_iotlb_memory = true;
}
- 这里一个slab是2K,io_tlb_nslabs表示有多少个slab,也即buffer是多大,默认buffer size是IO_TLB_DEFAULT_SIZE,即64M,也可以由bootloader通过swiotlb参数设置io_tlb_nslabs,io_tlb_nslabs会换算成buffer size。
- memblock_virt_alloc_low_nopanic用于从低地址物理页分配内存空间。
- swiotlb_init_with_tbl进一步初始化
mem_init->swiotlb_init->swiotlb_init_with_tbl
int __init swiotlb_init_with_tbl(char *tlb, unsigne