7.3 IOMMU流程分析

来源:互联网 发布:中小学排课软件 编辑:程序博客网 时间:2024/04/28 01:51


7.3.1 IOMMU 初始化

 kvm用到的iommu功能源码位于:drivers/iommu,主要代码为iommu.c, iova.c, dmar.c,intel-iommu.c.

 

7.3.1.1 IOMMU模块初始化

dmar.c: IOMMU_INIT_POST(detect_intel_iommu); (dmar是dma remapping缩写)

int __init detect_intel_iommu(void) {

    a. ret =dmar_table_detect(); //检察acpi表中是否存在dmar table

    b. 如果有则x86_init.iommu.iommu_init =intel_iommu_init;

}

 

arch\x86\kernel\pci_dma.c: rootfs_initcall(pci_iommu_init);

pci_iommu_init ==> x86_init.iommu.iommu_init;

 

int __init intel_iommu_init(void) {

a)  iommu_init_mempool();该模块要用到的内存池初始化iova, domain,dev_info三种cache

b)  dmar_table_init()遍历dmar table, 对每个damr设备建立一个intel_iommu *iommu;

c)  对每个intel_iommu,默认禁用转换iommu_disable_translation(iommu);

d)  遍历系统中的pci_dev,建立哪些dmar unit 管理哪些pci_dev的映射dmar_dev_scope_init

e)  init_no_remapping_devices();(一开始设置所有pci_dev都不用iommu,将

dev->archdata.iommu= DUMMY_DEVICE_DOMAIN_INFO);

f)  init_dmars,初始化dmar硬件并为每个dmar建立一个dmar_domain数组; 为pci_dev准备rmrr. rmrr区域的内存是vm host要保留出来使用的, 因此在为device建立iommu时要特别考虑(不建立iommu)

g)  dma_ops= &intel_dma_ops;

h)      bus_set_iommu(&pci_bus_type,&intel_iommu_ops);

bus_register_notifier(&pci_bus_type,&device_nb);

}

 

(1) ACPI 表中的先关结构

dmar_table_init  ==> parse_dmar_table

dmar的描述位于ACPI表中,每个dmar硬件设备能管理多个pci_dev.

dmar_parse_one_drhd 用于解析一个硬件,每个硬件对应一个intel_iommu结构.

struct dmar_drhd_unit {

    struct list_head list;      /* list of drhd units    */

    struct  acpi_dmar_header *hdr;  /* ACPI header       */

    u64 reg_base_addr;       /*硬件的寄存器基地址*/

    struct dmar_dev_scope *devices;/* 用于关联其上的pci设备*/

    int devices_cnt;      //pci设备数量,从acpi获得

    u16 segment;      /*PCI domain 从acpi获得   */

    u8  ignored:1;        /*ignore drhd       */

    u8  include_all:1;

    struct intel_iommu*iommu; // alloc_iommu函数创建,并初始化其寄存器地址与属性

};

关于寄存器的定义与acpi表中定义请参考intel vt-d spec.

dmar_table_init  ==> dmar_parse_one_rmrr

struct dmar_rmrr_unit {

    struct list_head list;      /* list of rmrr units    */

    struct acpi_dmar_header*hdr;   /* ACPI header       */

    u64 base_address;     /*reserved memory base_addr */

    u64 end_address;      /*reserved memory end address */

    struct dmar_dev_scope*devices; /* target devices */

    int devices_cnt;      /*target device count */

};

 

dmar_table_init  ==>  dmar_parse_one_atsr

struct dmar_atsr_unit {

    struct list_head list;      /* list of ATSR units */

    struct acpi_dmar_header*hdr;   /* ACPI header */

    struct dmar_dev_scope*devices; /* target devices */

    int devices_cnt;     /* target device count */

    u8 include_all:1;    /* include all ports */

};

 

(2) pci device与dmar的关系建立

int __init dmar_dev_scope_init(void){

。。。。。。

       for_each_pci_dev(dev){ //遍历pci device

           if(dev->is_virtfn)

              continue;

           info =dmar_alloc_pci_notify_info(dev,

                  BUS_NOTIFY_ADD_DEVICE);

           if (!info) {

              returndmar_dev_scope_status;

           } else {

              dmar_pci_bus_add_dev(info);

              dmar_free_pci_notify_info(info);

           }

       }

}

 

dmar_alloc_pci_notify_info 建立如下结构:

struct dmar_pci_notify_info {

    struct pci_dev           *dev;

    unsigned long        event;//为BUS_NOTIFY_ADD_DEVICE

    int           bus; //pci bus number

    u16           seg; //为:pci_domain_nr(dev->bus);

    u16           level; //在bus 结构上的层次

    structacpi_dmar_pci_path   path[];

} __attribute__((packed));

 

dmar_pci_bus_add_dev 根据_pci_notify_info,初始化dmar_drhd_unit->devices中的项

dmar_pci_bus_add_dev ==》 dmar_insert_dev_scope

    a.遍历dmar_unit,根据apci查看该dev是否和dmar_unit对应

    b.        for_each_dev_scope(devices,devices_cnt, i, tmp)

           if(tmp == NULL) {

              devices[i].bus= info->dev->bus->number;

              devices[i].devfn= info->dev->devfn;

              rcu_assign_pointer(devices[i].dev,

                        get_device(dev));

              return1;

           }完成dmar_unit与device号的关联

 

(3) intel_iommu与dmar_domain的绑定

init_dmars

a)  对每个dmar unit 作

ret = iommu_init_domains(iommu);

iommu_alloc_root_entry(iommu);

b)  对每个dmar unit (该步骤为寄存器操作)

清楚错误状态dmar_fault(-1, iommu);

禁止queued invalidation dmar_disable_qi(iommu);

c)  开启queued invalidation

dmar_enable_qi; 需要一个page的物理内存 ==》

1.  dmar_writeq(iommu->reg + DMAR_IQA_REG,virt_to_phys(qi->desc));

iommu->flush.flush_context = qi_flush_context;

iommu->flush.flush_iotlb = qi_flush_iotlb;

d)  queued invalidation用于iotlb, 可以一次提交多个invalidation请求

e)  为设备建立iommu等值映射(物理地址=虚拟地址)

iommu_prepare_static_identity_mapping

f)  对每个dmar,

开启dmar 硬件的中断dmar_set_interrupt

设置root_entry的物理地址iommu_set_root_entry

使能iommu iommu_enable_translation

 

iommu_init_domains ==> 一个iommu有多个domain,domain数量有寄存器DMAR_CAP_REG决定。

iommu_alloc_root_entry ==>

    每个iommu对应一个iommu->root_entry= alloc_pgtable_page(iommu->node);;

 

iommu_prepare_static_identity_mapping:

a)  si_domain_init为iommu添加一个domain,该domain为等值映射domain

b)  将pci device加入到等值映射domain中去 dev_prepare_static_identity_mapping

其中dev_prepare_static_identity_mapping ==>  iommu_should_identity_map==>device_has_rmrr会对有rmrr的device作特殊处理

c)  对acpi中的设备加入到等值映射domain中去 dev_prepare_static_identity_mapping

 

si_domain_init ==>

    a. si_domain =alloc_domain(false);

    b. 对所有iommu iommu_attach_domain(si_domain, iommu);

    c. 为所有物理内存做等值映射iommu_domain_identity_map

 

iommu_domain_identity_map:

a)  unsignedlong first_vpfn = start >> VTD_PAGE_SHIFT;

unsignedlong last_vpfn = end >> VTD_PAGE_SHIFT;

reserve_iova(&domain->iovad,dma_to_mm_pfn(first_vpfn),

dma_to_mm_pfn(last_vpfn)); //为物理内存建立一个iova虚拟内存区域,iova用于管理gpa区域

b)  __domain_mapping完成iommu设置(下一节会分析该函数)

 

dev_prepare_static_identity_mapping==》domain_add_dev_info

a. 每个dev 对应一个device_domain_info

    info->bus = bus;

    info->devfn = devfn;

    info->dev = dev;

    info->domain =domain;

    info->iommu = iommu;

b. 将info加入到list_add(&info->link, &domain->devices);中

c. dev->archdata.iommu = info;

 

dev_prepare_static_identity_mapping==》domain_context_mapping ==>

domain_context_mapping_one: 将pci_device根据bus 号加入到

    iommu->root_entry[bus]中,

    root =&iommu->root_entry[bus];

    context =get_context_addr_from_root(root);

    entry = context[devfn]为设备对应的实际entry位置

关联entry 与(domain->id, domain->pgd)

 

通过上面分析发现当iommu开启时:

(1)驱动为所有pcidevice做了物理地址与虚拟地址等值映射的domain.

(2) intel_iommu对iommu层提供了接口bus_set_iommu(&pci_bus_type, &intel_iommu_ops)

static struct iommu_ops intel_iommu_ops = {

    .domain_init  = intel_iommu_domain_init,

    .domain_destroy =intel_iommu_domain_destroy,

    .attach_dev   = intel_iommu_attach_device,

    .detach_dev   = intel_iommu_detach_device,

    .map       = intel_iommu_map,

    .unmap     = intel_iommu_unmap,

    .iova_to_phys = intel_iommu_iova_to_phys,

    .domain_has_cap =intel_iommu_domain_has_cap,

    .add_device   = intel_iommu_add_device,

    .remove_device    = intel_iommu_remove_device,

    .pgsize_bitmap    = INTEL_IOMMU_PGSIZES,

};

下一节将分析kvm中对iommu的调用和iommu映射的建立过程

 

7.3.2 IOMMU Map

(1) attach

iommu_attach_device(struct iommu_domain *domain, struct device*dev)

==> domain->ops->attach_dev(domain, dev);

 

由于在bus_set_iommu(&pci_bus_type, &intel_iommu_ops)

kvm_vm_ioctl_assign_device ==》 kvm_iommu_map_guest ==》

kvm->arch.iommu_domain =iommu_domain_alloc(&pci_bus_type);

 

而struct iommu_domain *iommu_domain_alloc(struct bus_type *bus)

{

    。。。。。。

    domain = kzalloc(sizeof(*domain),GFP_KERNEL);

    domain->ops = bus->iommu_ops;

    ret =domain->ops->domain_init(domain);

    if (ret)

       goto out_free;

 

    return domain;

}

而在bus_set_iommu(&pci_bus_type, &intel_iommu_ops) 会使

bus->iommu_ops = intel_iommu_ops; 所以domain->ops->attachment = intel_iommu_attach_device.同时domain->ops->domain_init =intel_iommu_domain_init

intel_iommu_domain_init (struct iommu_domain *domain) {

    dmar_domain =alloc_domain(true);

    domain->priv =dmar_domain;

}

 

intel_iommu_attach_device(struct iommu_domain *domain,

                   struct device *dev)

a. 若设备已有domain,则移除原来的domain, domain_remove_dev_info

  这样原来等值映射的domain被移除了

b. domain_add_dev_info 将设备和新的domain关联

 

(2) iommu map

kvm_iommu_map_pages ==> iommu_map(domain, gfn_to_gpa(gfn),pfn_to_hpa(pfn),

                page_size, flags);

iommu_map(struct iommu_domain *domain, unsigned long iova,

          phys_addr_t paddr, size_t size, int prot)

{

    .........

    while (size) {

       size_t pgsize =iommu_pgsize(domain, iova | paddr, size);

       //iova 为gpa, paddr 为hpa

       ret =domain->ops->map(domain, iova, paddr, pgsize, prot);

       if (ret)

           break;

 

       iova += pgsize;

       paddr += pgsize;

       size -= pgsize;

    }

}

intel_iommu_map(struct iommu_domain *domain,

              unsigned long iova, phys_addr_t hpa,

              size_t size, int iommu_prot) ==>

domain_pfn_mapping(dmar_domain, iova >> VTD_PAGE_SHIFT,

               hpa >> VTD_PAGE_SHIFT, size, prot);==> __domain_mapping

映射的目的是能更具iov_pfn得到hpa地址, 该过程类似为虚拟地址和物理地址映射的过程,简化后的代码如下:

    prot &=DMA_PTE_READ | DMA_PTE_WRITE | DMA_PTE_SNP;

    {

       sg_res = nr_pages +1;

       pteval =((phys_addr_t)phys_pfn << VTD_PAGE_SHIFT) | prot;

    }

 

    while (nr_pages > 0){

       uint64_t tmp;

 

       if (!pte) {

           largepage_lvl =hardware_largepage_caps(domain, iov_pfn, phys_pfn, sg_res);

         

           first_pte = pte= pfn_to_dma_pte(domain, iov_pfn, &largepage_lvl);

 

           if(largepage_lvl > 1) {

              pteval |=DMA_PTE_LARGE_PAGE;

          

              dma_pte_clear_range(domain,iov_pfn,

                         iov_pfn + lvl_to_nr_pages(largepage_lvl) -1);

              dma_pte_free_pagetable(domain,iov_pfn,

                         iov_pfn + lvl_to_nr_pages(largepage_lvl) -1);

           } else {

              pteval &=~(uint64_t)DMA_PTE_LARGE_PAGE;

           }

 

       }

       tmp =cmpxchg64_local(&pte->val, 0ULL, pteval);

 

       lvl_pages =lvl_to_nr_pages(largepage_lvl);

 

       nr_pages -=lvl_pages;

       iov_pfn +=lvl_pages;

       phys_pfn +=lvl_pages;

       pteval += lvl_pages* VTD_PAGE_SIZE;

       sg_res -= lvl_pages;

   

       pte++;

       if (!nr_pages ||first_pte_in_page(pte) ||

           (largepage_lvl > 1 && sg_res< lvl_pages)) {

           domain_flush_cache(domain,first_pte,

                     (void *)pte - (void *)first_pte);

           pte = NULL;

       }

    }

这里要补充的一个概念是agaw时客户机地址宽度修正值,如host的物理内存是4GB,而客户机内存为2G, 所以GAW为31. AGAW为:

static inline int guestwidth_to_adjustwidth(int gaw) {

    int agaw;

    int r = (gaw - 12) % 9;

    if (r == 0)

       agaw = gaw;

    else

       agaw = gaw + 9 - r;

    if (agaw > 64)

       agaw = 64;

    return agaw;

}

agaw决定了IO多级页表的级数:

agaw

Io页表级数

30

2

39

3

48

4

57

5

64

6

 

0 0
原创粉丝点击