smmu之关于iommu.strict的原理

前言

前段时间测试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功能。

 

posted @ 2021-03-06 23:32  haoxing990  阅读(1458)  评论(0编辑  收藏  举报