DMAR(DMA remapping)与 IOMMU

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/interrupts
            CPU0       CPU1       CPU2       CPU3       
   0:        127          0          0          0  IR-IO-APIC-edge      timer
   1:          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 48
    IRQ: 48
 STATUS: 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: ffff880439deb8c0
          handler: ffffffff812ad9b0  <dmar_fault>
            flags: 0
             name: ffff880439ca9180  "dmar0"
           dev_id: ffff880439ca9140
             next: 0
  DEPTH: 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

 

  • 3
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值