《ARM SMMU原理与IOMMU技术(“VT-d” DMA、I/O虚拟化、内存虚拟化)》

《提升KVM异构虚拟机启动效率:透传(pass-through)、DMA映射(VFIO、PCI、IOMMU)、virtio-balloon、异步DMA映射、预处理》

《内核引导参数IOMMU与INTEL_IOMMU有何不同?》

《DMAR(DMA remapping)与 IOMMU》

在分析Linux kernel dump的时候经常会看到一个叫做dmar的东西,查看中断信息的时候也时常见到一个名称为dmar0的设备,到底什么是dmar呢?

$ cat /proc/interruptsCPU0       CPU1       CPU2       CPU3       0:        127          0          0          0  IR-IO-APIC-edge      timer1:          2          0          0          0  IR-IO-APIC-edge      i8042...48:          0          0          0          0  DMAR_MSI-edge        dmar0...

大家知道,I/O设备可以直接存取内存,称为DMA(Direct Memory Access);DMA要存取的内存地址称为DMA地址(也可称为BUS address)。在DMA技术刚出现的时候,DMA地址都是物理内存地址,简单直接,但缺点是不灵活,比如要求物理内存必须是连续的一整块而且不能是高位地址等等,也不能充分满足虚拟机的需要。后来dmar就出现了。 dmar意为DMA remapping,是Intel为支持虚拟机而设计的I/O虚拟化技术,I/O设备访问的DMA地址不再是物理内存地址,而要通过DMA remapping硬件进行转译,DMA remapping硬件会把DMA地址翻译成物理内存地址,并检查访问权限等等。负责DMA remapping操作的硬件称为IOMMU。做个类比:大家都知道MMU是支持内存地址虚拟化的硬件,MMU是为CPU服务的;而IOMMU是为I/O设备服务的,是将DMA地址进行虚拟化的硬件。  

IOMMA不仅将DMA地址虚拟化,还起到隔离、保护等作用,如下图所示意,详细请参阅Intel Virtualization Technology for Directed I/O

现在我们知道了dmar的概念,那么Linux中断信息中出现的dmar0又是什么呢? 还是用MMU作类比吧,便于理解:当CPU访问一个在地址翻译表中不存在的地址时,就会触发一个fault,Linux kernel的fault处理例程会判断这是合法地址还是非法地址,如果是合法地址,就分配相应的物理内存页面并建立从物理地址到虚拟地址的翻译表项,如果是非法地址,就给进程发个signal,产生core dump。IOMMU也类似,当I/O设备进行DMA访问也可能触发fault,有些fault是recoverable的,有些是non-recoverable的,这些fault都需要Linux kernel进行处理,所以IOMMU就利用中断(interrupt)的方式呼唤内核,这就是我们在/proc/interrupts中看到的dmar0那一行的意思。 我们看到的中断号48,据此还可以进一步发掘更多的信息:

crash64> irq 48IRQ: 48STATUS: 100 (IRQ_INPROGRESS)
HANDLER: ffffffff81a96a40            <dmar_msi_type>typename: ffffffff81791acb  "DMAR_MSI"startup: ffffffff810e3960  <default_startup>shutdown: ffffffff810e3920  <default_shutdown>enable: ffffffff810e3990  <default_enable>disable: ffffffff810e3860  <default_disable>ack: ffffffff81031a00  <ack_apic_edge>mask: ffffffff812add60  <dmar_msi_mask>mask_ack: 0  unmask: ffffffff812addc0  <dmar_msi_unmask>eoi: 0  end: ffffffff810e14f0  <noop>set_affinity: ffffffff81033130  <dmar_msi_set_affinity>retrigger: ffffffff81031260  <ioapic_retrigger_irq>set_type: 0  set_wake: 0  ACTION: ffff880439deb8c0handler: ffffffff812ad9b0  <dmar_fault>flags: 0name: ffff880439ca9180  "dmar0"dev_id: ffff880439ca9140next: 0DEPTH: 0

上面最有意思的信息是ACTION的handler,表示IOMMU发生fault之后的中断处理例程,我们看到的例程名是dmar_fault,源代码如下:

1296 irqreturn_t dmar_fault(int irq, void *dev_id)
1297 {
1298         struct intel_iommu *iommu = dev_id;
1299         int reg, fault_index;
1300         u32 fault_status;
1301         unsigned long flag;
1302
1303         spin_lock_irqsave(&iommu->register_lock, flag);
1304         fault_status = readl(iommu->reg + DMAR_FSTS_REG);
1305         if (fault_status)
1306                 pr_err("DRHD: handling fault status reg %x\n", fault_status);
1307
1308         /* TBD: ignore advanced fault log currently */
1309         if (!(fault_status & DMA_FSTS_PPF))
1310                 goto clear_rest;
1311
1312         fault_index = dma_fsts_fault_record_index(fault_status);
1313         reg = cap_fault_reg_offset(iommu->cap);
1314         while (1) {
1315                 u8 fault_reason;
1316                 u16 source_id;
1317                 u64 guest_addr;
1318                 int type;
1319                 u32 data;
1320
1321                 /* highest 32 bits */
1322                 data = readl(iommu->reg + reg +
1323                                 fault_index * PRIMARY_FAULT_REG_LEN + 12);
1324                 if (!(data & DMA_FRCD_F))
1325                         break;
1326
1327                 fault_reason = dma_frcd_fault_reason(data);
1328                 type = dma_frcd_type(data);
1329
1330                 data = readl(iommu->reg + reg +
1331                                 fault_index * PRIMARY_FAULT_REG_LEN + 8);
1332                 source_id = dma_frcd_source_id(data);
1333
1334                 guest_addr = dmar_readq(iommu->reg + reg +
1335                                 fault_index * PRIMARY_FAULT_REG_LEN);
1336                 guest_addr = dma_frcd_page_addr(guest_addr);
1337                 /* clear the fault */
1338                 writel(DMA_FRCD_F, iommu->reg + reg +
1339                         fault_index * PRIMARY_FAULT_REG_LEN + 12);
1340
1341                 spin_unlock_irqrestore(&iommu->register_lock, flag);
1342
1343                 dmar_fault_do_one(iommu, type, fault_reason,
1344                                 source_id, guest_addr);
1345
1346                 fault_index++;
1347                 if (fault_index >= cap_num_fault_regs(iommu->cap))
1348                         fault_index = 0;
1349                 spin_lock_irqsave(&iommu->register_lock, flag);
1350         }
1351 clear_rest:
1352         /* clear all the other faults */
1353         fault_status = readl(iommu->reg + DMAR_FSTS_REG);
1354         writel(fault_status, iommu->reg + DMAR_FSTS_REG);
1355
1356         spin_unlock_irqrestore(&iommu->register_lock, flag);
1357         return IRQ_HANDLED;
1358 }

请注意行号1343,dmar_fault_do_one()会报告fault的具体信息,包括对应设备的物理位置。由于一个dmar对应着很多个I/O设备,这条信息可以帮助定位到具体哪一个设备。源代码如下:

1270 static int dmar_fault_do_one(struct intel_iommu *iommu, int type,
1271                 u8 fault_reason, u16 source_id, unsigned long long addr)
1272 {
1273         const char *reason;
1274         int fault_type;
1275
1276         reason = dmar_get_fault_reason(fault_reason, &fault_type);
1277
1278         if (fault_type == INTR_REMAP)
1279                 pr_err("INTR-REMAP: Request device [[%02x:%02x.%d] "
1280                        "fault index %llx\n"
1281                         "INTR-REMAP:[fault reason %02d] %s\n",
1282                         (source_id >> 8), PCI_SLOT(source_id & 0xFF),
1283                         PCI_FUNC(source_id & 0xFF), addr >> 48,
1284                         fault_reason, reason);
1285         else
1286                 pr_err("DMAR:[%s] Request device [%02x:%02x.%d] "
1287                        "fault addr %llx \n"
1288                        "DMAR:[fault reason %02d] %s\n",
1289                        (type ? "DMA Read" : "DMA Write"),
1290                        (source_id >> 8), PCI_SLOT(source_id & 0xFF),
1291                        PCI_FUNC(source_id & 0xFF), addr, fault_reason, reason);
1292         return 0;
1293 }

dmar的初始化是kernel根据ACPI中的dmar table进行的,每一个表项对应一个dmar设备,名称从dmar0开始依次递增,涉及取名的代码如下:

0751 int alloc_iommu(struct dmar_drhd_unit *drhd)
0752 {
0753         struct intel_iommu *iommu;
0754         u32 ver;
0755         static int iommu_allocated = 0;
0756         int agaw = 0;
0757         int msagaw = 0;
0758         int err;
0759
0760         if (!drhd->reg_base_addr) {
0761                 warn_invalid_dmar(0, "");
0762                 return -EINVAL;
0763         }
0764
0765         iommu = kzalloc(sizeof(*iommu), GFP_KERNEL);
0766         if (!iommu)
0767                 return -ENOMEM;
0768
0769         iommu->seq_id = iommu_allocated++;
0770         sprintf (iommu->name, "dmar%d", iommu->seq_id);
...
0797         pr_info("IOMMU %d: reg_base_addr %llx ver %d:%d cap %llx ecap %llx\n",
0798                 iommu->seq_id,
0799                 (unsigned long long)drhd->reg_base_addr,
0800                 DMAR_VER_MAJOR(ver), DMAR_VER_MINOR(ver),
0801                 (unsigned long long)iommu->cap,
0802                 (unsigned long long)iommu->ecap);
...

上面也揭示了boot过程留在dmesg中信息的来历:

dmar: IOMMU 0: reg_base_addr f8ffe000 ver 1:0 cap d2078c106f0462 ecap f020fe

附注: 过去的AMD64芯片也提供一个功能有限的地址转译模块——GART (Graphics Address Remapping Table),有时候它也可以充当IOMMU,这导致了人们对GART和新的IOMMU的混淆。最初设计GART是为了方便图形芯片直接读取内存:使用地址转译功能将收集到内存中的数据映射到一个图形芯片可以“看”到的地址。后来GART被Linux kernel用来帮助传统的32位PCI设备访问可寻址范围之外的内存区域。这件事新的IOMMU当然也可以做到,而且没有GART的局限性(它仅限于显存的范围之内),IOMMU可以将I/O设备的任何DMA地址转换为物理内存地址。


参考资料:

  1. Linux Kernel的Intel-IOMMU.txt
  2. Intel’s Virtualization for Directed I/O (a.k.a IOMMU)
  3. Wikipedia IOMMU
  4. 理解IOMMU、北桥、MMIO和ioremap
  5. Intel Virtualization Technology for Directed I/O
  6. DMAR 与 IOMMU

DMAR(DMA remapping)与 IOMMU相关推荐

  1. iommu 工作原理解析之dma remapping

    深入了解iommu系列二:iommu 工作原理解析之dma remapping: https://zhuanlan.zhihu.com/p/479963917

  2. VFIO - 将 DMA 映射暴露给用户态

    <ARM SMMU原理与IOMMU技术("VT-d" DMA.I/O虚拟化.内存虚拟化)> <提升KVM异构虚拟机启动效率:透传(pass-through).DM ...

  3. Linux驱动:VFIO概述(vfio/iommu/device passthrough)

    <ARM SMMU原理与IOMMU技术("VT-d" DMA.I/O虚拟化.内存虚拟化)> <提升KVM异构虚拟机启动效率:透传(pass-through).DM ...

  4. intel linux 开发板,Intel IOMMU在Linux上的实现架构

    1.检测平台是否支持DMAR设备 ./drivers/pci/dmar.c->int __init early_dmar_detect(void) { acpi_status status = ...

  5. vfio概述(vfio/iommu/device passthrough)

    文章目录 1.IOMMU 1.1 IOMMU功能简介 1.2 IOMMU作用 1.3 IOMMU工作原理 1.4 Source Identifier 2.VFIO 2.1 概念介绍 2.2 使用示例 ...

  6. I/O 虚拟化技术 — IOMMU

    目录 文章目录 目录 IOMMU - CPU 硬件支撑的 I/O 虚拟化方案 需求背景 DMA Remapping Feature IOMMU 硬件单元 PCI Passthrough 开启 IOMM ...

  7. Linux 操作系统原理 — 网络 I/O 虚拟化

    目录 文章目录 目录 IOMMU - CPU 硬件支撑的 I/O 虚拟化方案 需求背景 DMA Remapping Feature IOMMU 硬件单元 PCI Passthrough 开启 IOMM ...

  8. linux用户层驱动--VFIO(四)

    VFIO--将设备暴露到用户态 在开始之前我们先要说一个东西就是 DMA,直接让设备访问内存,可以不通过 CPU 搬运数据. 这是一个比较简单的体系结构图,设备 和 CPU 通过存储控制器访问存储器. ...

  9. (WIP)Start my first kernel journey (by quqi99)

    作者:张华  发表于:2016-03-22 版权声明:可以任意转载,转载时请务必以超链接形式标明文章原始出处和作者信息及本版权声明 ( http://blog.csdn.net/quqi99 ) 在内 ...

最新文章

  1. 拖拽公式图片、一键转换LaTex公式,开源公式识别神器
  2. Pivotal发布Spring Cloud Data Flow 1.5版本
  3. 杭电2037java实现
  4. Golang的基本类型、引用类型、复合类型
  5. 蓝色企业CMS网站后台管理模板
  6. Does taro support react hook?
  7. matlab乘幂的指数是矩阵,信号与系统MATLAB基本语法.ppt
  8. 【redis】redis实用Utils
  9. 将CMD内的显示内容输出到txt文件
  10. 轻量级持久存储系统 MemcacheDB
  11. 计算机一级插入页眉,2017年计算机一级WPS辅导:WPS中页眉页脚的设计技巧
  12. 如何制作SCI论文中的Figure(三)
  13. 基于SSM的小区报修系统
  14. JTA分布式事务处理
  15. 方舟无限资源服务器,方舟生存进化怎么无限资源
  16. JAVA端收集Liunx服务器 CPU 内存 磁盘使用率
  17. 什么是动态代理,动态代理的应用有哪些
  18. word转freemarker和修改的步骤
  19. Livid: 消失的未来
  20. 局域网bs虚拟服务器怎么创建,搭建局域网地图服务器

热门文章

  1. Element - Vue UI Framework
  2. Docker多机创建mysqlCluster 8.0群集
  3. Java基础-通过反射获取类的信息
  4. Oracle EBS AP 发票放弃行
  5. 使用VS Code开发asp.net core (上)
  6. H5前端性能测试总结
  7. PowerDesigner 学习
  8. Java JDBC初步
  9. 页面传值,发生错误,如何传递中文信息
  10. 为什么实施的项目会失败??