前言
前段时间测试smmu的性能的时候开启和关闭strict功能,对比了strict开启和关闭后的差异,
竟然发现差异还挺大的,就想弄明白这个功能是咋实现的。
strict的原理
其实了解这个功能的最好方式还是看该系列patch,这里列出patch和作者的解释
https://patchwork.kernel.org/project/linux-arm-kernel/patch/c98d9eaa24dbfcbd6ee3fc7b697538cb494fcf13.1536856828.git.robin.murphy@arm.com/
1. Save the related domain pointer in struct iommu_dma_cookie, make iovad capable call domain->ops->flush_iotlb_all to flush TLB. 2. During the iommu domain initialization phase, base on domain->non_strict field to check whether non-strict mode is supported or not. If so, call init_iova_flush_queue to register iovad->flush_cb callback. 3. All unmap(contains iova-free) APIs will finally invoke __iommu_dma_unmap -->iommu_dma_free_iova. If the domain is non-strict, call queue_iova to put off iova freeing, and omit iommu_tlb_sync operation.
大体意思是dma在unmap的时候会频繁的调用iommu_tlb_sync函数,所以增加了no_stirct bool变量,在不支持no_strict变量的时候
会直接调用iommu_tlb_sync函数对tlb进行刷新,如果支持no_strict, 就会把这次sync放在queue_iova队列中去做,这样不必每次
umap的时候都去调用iommu_tlb_sync函数,这样对smmu的性能提升是很有帮助的。
我们再来看看queue_iova到底是个什么东西。
458 static void __iommu_dma_unmap(struct device *dev, dma_addr_t dma_addr, 459 size_t size) 460 { 461 struct iommu_domain *domain = iommu_get_dma_domain(dev); 462 struct iommu_dma_cookie *cookie = domain->iova_cookie; 463 struct iova_domain *iovad = &cookie->iovad; 464 size_t iova_off = iova_offset(iovad, dma_addr); 465 struct iommu_iotlb_gather iotlb_gather; 466 size_t unmapped; 467 468 dma_addr -= iova_off; 469 size = iova_align(iovad, size + iova_off); 470 iommu_iotlb_gather_init(&iotlb_gather); 471 472 unmapped = iommu_unmap_fast(domain, dma_addr, size, &iotlb_gather); 473 WARN_ON(unmapped != size); 474 475 if (!cookie->fq_domain) 476 iommu_iotlb_sync(domain, &iotlb_gather); 477 iommu_dma_free_iova(cookie, dma_addr, size);
442 static void iommu_dma_free_iova(struct iommu_dma_cookie *cookie, 443 dma_addr_t iova, size_t size) 444 { 445 struct iova_domain *iovad = &cookie->iovad; 446 447 /* The MSI case is only ever cleaning up its most recent allocation */ 448 if (cookie->type == IOMMU_DMA_MSI_COOKIE) 449 cookie->msi_iova -= size; 450 else if (cookie->fq_domain) /* non-strict mode */ 451 queue_iova(iovad, iova_pfn(iovad, iova), 452 size >> iova_shift(iovad), 0); 453 else 454 free_iova_fast(iovad, iova_pfn(iovad, iova), 455 size >> iova_shift(iovad)); 456 }
从代码调用流程可以看出,dma请求完成后,发送umap操作是否dma映射的地址时候,会调用queue_iova函数,
549 void queue_iova(struct iova_domain *iovad, 550 unsigned long pfn, unsigned long pages, 551 unsigned long data) 552 { 553 struct iova_fq *fq = raw_cpu_ptr(iovad->fq); 554 555 idx = fq_ring_add(fq); 556 557 fq->entries[idx].iova_pfn = pfn; 558 fq->entries[idx].pages = pages; 559 fq->entries[idx].data = data; 560 fq->entries[idx].counter = atomic64_read(&iovad->fq_flush_start_cnt); 561 562 spin_unlock_irqrestore(&fq->lock, flags); 563 564 /* Avoid false sharing as much as possible. */ 565 if (!atomic_read(&iovad->fq_timer_on) && 566 !atomic_xchg(&iovad->fq_timer_on, 1)) 567 mod_timer(&iovad->fq_timer, 568 jiffies + msecs_to_jiffies(IOVA_FQ_TIMEOUT)); 569 } 570 EXPORT_SYMBOL_GPL(queue_iova);
而该函数会将该操作添加fq队列,并且设timer,到期再做处理。
我们再看看fq和fq_timer是一个什么东东。
79 int init_iova_flush_queue(struct iova_domain *iovad, 80 iova_flush_cb flush_cb, iova_entry_dtor entry_dtor) 81 { 82 struct iova_fq __percpu *queue; 83 int cpu; 84 85 atomic64_set(&iovad->fq_flush_start_cnt, 0); 86 atomic64_set(&iovad->fq_flush_finish_cnt, 0); 87 88 queue = alloc_percpu(struct iova_fq); 89 if (!queue) 90 return -ENOMEM; 91 92 iovad->flush_cb = flush_cb; 93 iovad->entry_dtor = entry_dtor; 94 95 for_each_possible_cpu(cpu) { 96 struct iova_fq *fq; 97 98 fq = per_cpu_ptr(queue, cpu); 99 fq->head = 0; 100 fq->tail = 0; 101 102 spin_lock_init(&fq->lock); 103 } 104 105 smp_wmb(); 106 107 iovad->fq = queue; 108 109 timer_setup(&iovad->fq_timer, fq_flush_timeout, 0); 110 atomic_set(&iovad->fq_timer_on, 0); 111 112 return 0; 113 }
这个函数基本就明白no_strict的设计原理了,首先定义一个per cpu队列并未该队列设置一个timer, 通过timeout, 定期调用其回调函flush_cb进行flush操作处理,这样对于并不急于
flush tlb的device来说,推后处理并没有什么影响,同样不过多占用cpu,一举两得。
301 static int iommu_dma_init_domain(struct iommu_domain *domain, dma_addr_t base, 302 u64 size, struct device *dev) 303 { 304 305 if (!cookie->fq_domain && !iommu_domain_get_attr(domain, 306 DOMAIN_ATTR_DMA_USE_FLUSH_QUEUE, &attr) && attr) { 307 if (init_iova_flush_queue(iovad, iommu_dma_flush_iotlb_all, 308 NULL)) 309 pr_warn("iova flush queue initialization failed\n"); 310 else 311 cookie->fq_domain = domain; 312 } 313 314 if (!dev) 315 return 0; 316 317 return iova_reserve_iommu_regions(dev, domain); 318 }
由于在传入iommu.strict=0的时候,attr值被set_attr设置为1了,所以该判定是对的,所以会调用该函数,并且设置flush_cb回调函数为iommu_dma_flush_iotlb_all。后面的就不用再做过多的分析了。
strict赋值流程
只需要在cmdline加上iommu.strict=0/1即可关闭和开启strict功能,通过cmdline传入的该参数会被
iommu.c 330:early_param("iommu.strict", iommu_dma_setup); 324 early_param("iommu.passthrough", iommu_set_def_domain_type); 325 326 static int __init iommu_dma_setup(char *str) 327 { 328 return kstrtobool(str, &iommu_dma_strict); // 通过改变了决定是否支持strict功能。 329 } 330 early_param("iommu.strict", iommu_dma_setup); static bool iommu_dma_strict __read_mostly = true; 1494 if (!iommu_dma_strict) { 1495 int attr = 1; 1496 iommu_domain_set_attr(dom, 1497 DOMAIN_ATTR_DMA_USE_FLUSH_QUEUE, 1498 &attr); 此处特别要要注意attr这个传入值,这个传入值和no_driect有着直接的关系。iommu_domain_set_attr最终会调用到各平台实现的set_attr函数,这里以arm smmu凭条为例 2442 static int arm_smmu_domain_set_attr(struct iommu_domain *domain, 2443 enum iommu_attr attr, void *data) 2444 { 2445 int ret = 0; 2446 case IOMMU_DOMAIN_DMA: 2447 switch(attr) { 2448 case DOMAIN_ATTR_DMA_USE_FLUSH_QUEUE: 2449 smmu_domain->non_strict = *(int *)data; 2450 break; 2451 default: 2452 ret = -ENODEV; 2453 } 2454 break; 2455 default: 2456 ret = -EINVAL; 2457 } 可以看出在iommu传入的attr=1的值被赋予了non_strict,此处no_strict=1,说明不开启 strict功能。