• 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
    ");
     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功能。
    是时候好好总结下自己走过的路。
  • 相关阅读:
    MySQL 知识点
    用PHP操作http中Etag、lastModified和Expires标签
    Open Flash Chart在php中的使用教程
    Cmake,source_group
    Cmake调用NSIS(一个可执行文件,其实就是一个编译器)编译NSIS脚本问题研究
    VS2010安装与测试编译问题(fatal error LNK1123: failure during conversion to COFF: file invalid or corrupt)
    Cmake find_package()相关
    Cmake,链接一个外部(也可能是第三方,也可能是自己编译的)库
    逆向工程入门指南
    Cmake的install与file命令的区别
  • 原文地址:https://www.cnblogs.com/haoxing990/p/14493040.html
Copyright © 2020-2023  润新知