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
- 7.3 IOMMU流程分析
- iommu
- IOMMU
- IOMMU简介
- IOMMU howto
- IOMMU Overview
- 什么是IOMMU
- Intel IOMMU
- 什么是IOMMU
- IOMMU概念,优点,缺点
- IOMMU简述-上篇
- IOMMU相关概念
- VFIO IOMMU UIO ...
- LightsService分析 --- 流程分析
- msm8996的ION, dma-buf, iommu, dma-iommu
- Intel IOMMU on Linux kernel
- IOMMU comes to Solaris x86
- AMD(IOMMU) / Intel (VT-d)
- POJ 2062Card Game Cheater
- [HDOJ 4524] 郑厂长系列故事――逃离迷宫
- Android中的多线程编程(二)Handler的原理(附源码)
- 【YII2学习笔记】20150726-1
- 抛开flash,自己开发实现C++ RTMP直播流播放器
- 7.3 IOMMU流程分析
- 专家术语学习机
- Spark Core---从作业提交到任务调度完整生命周期浅析
- POJ 3548Restoring the digits
- 8.1 Qemu 管理模块与接口
- CF_540C_IceCave
- 安卓内核程序安装机制--
- [FOJ 2121] 神庙逃亡
- POJ 3093Margaritas on the River Walk