iommu & intel-iommu实现

本文深入探讨Linux中IOMMU(输入输出内存管理单元)与intel-iommu的具体实现,涵盖iommu、domain、group、device等关键数据结构的关联与映射原理。同时,解析VFIO(虚拟化I/O框架)设备透传机制,包括container与group的交互,以及如何通过VFIO实现IOMMU设备的高效管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

iommu & intel-iommu

本篇文章主要针对iommu和intel-iommu讲述linux代码具体实现。同时由于这里整体实现代码较多,文章主要从iommu、domain、group、device这几个数据结构出发,阐述这几个数据结构如何建立关联,并讲解如果建立的iommu映射。先不考虑vfio的情况,只考虑物理机开启iommu的情况。第二部分会针对vfio做讨论。

iommu、domain、group、device

/*
 * 整个实现代码挺多,不对所有细节进行描述,主要针对iommu、domain、group、device之间的关系进行讲解,以便更 
 * 好理解代码实现
 */
intel_iommu_init
  -->根据no_iommu及dmar_disabled判断是否开启iommu
  ——>init_dmars
     -->初始化各个iommu
     -->根据iommu_identity_mapping初始化si_domain_init,当前内核没有采用,具体作用见下面分析
  -->bus_set_iommu
     -->iommu_bus_init
        -->add_iommu_group   /* 遍历所有设备执行  */
            -->intel_iommu_add_device
               -->device_to_iommu   /* 根据dmar_drhd_unit找到当前device属于哪个intel_iommu */
               -->iommu_device_link  /* 创建 /sys/dmarxx */
               -->iommu_group_get_for_dev
               /* 找到设备的iommu_group, 起初时候必然没有,所以创建 */
                  -->iommu_group_get   
                  -->iommu_group_get_for_pci_dev   
                     -->按照iommu_group定义分配组,同一个group是可以共享的
                     -->iommu_group_alloc
                     -->group->default_domain = __iommu_domain_alloc  /* 这里其实并没有分配,因为type类型为IOMMU_DOMAIN_DMA会直接返回 */
                     -->group->domain = group->default_domain
                  -->iommu_group_add_device
                     -->dev->iommu_group = group    /* device 和 iommu_group 关联  */
                     -->iommu_group_create_direct_mappings  /* AMD */
                     -->list_add_tail(&device->list, &group->devices) /* group list */
                     -->__iommu_attach_device(group->domain, dev) 
                       /* 按照vt-d,一个group属于可以隔离的最小单元,并且同一个group srcid相同,所以一个group可以有一个domain,domain和device也要关联起来,但是此时group->domain为空,所以这下面并不会执行 */
                        //-->intel_iommu_attach_device
                           //-->domain_add_dev_info
                              //-->dmar_insert_one_dev_info
                                 //-->device_domain_info *info = alloc_devinfo_mem();
                                 //-->domain_attach_iommu(domain, iommu) /*domain 和 iommu关联*/
                                 //-->list_add(&info->link, &domain->devices)
                                 //-->list_add(&info->global, &device_domain_list)
                                 //-->dev->archdata.iommu = info
                                 //-->domain_context_mapping  /*这一步很重要,这里面将domain的页表地址放入了其对应的src项中  */
                               
             
                                 

那么现在总结下来就是一个group和一个domain相对应,因为一个group拥有一个src id,而一个iommu根据16bit src id支持2 ^ 16 = 65536张页表,一张页表可以理解为一个domain,不过这里有一个问题,就是开启iommu之后,物理机上的多台设备是不是可以共用一个domain,这样会省去很大的页表开销。确实,内核可以通过启动参数来实现这个功能,下面介绍下iommu相关的一些启动参数。

内核提供了一系列启动参数,下面针对主要的几个启动参数来讲解:

int no_iommu __read_mostly;

#ifdef CONFIG_INTEL_IOMMU_DEFAULT_ON
int dmar_disabled = 0;
#else
int dmar_disabled = 1;
#endif /*CONFIG_INTEL_IOMMU_DEFAULT_ON*/

/* 这两个又一个为1,都不会使用iommu  ,其中 no_iommu 默认为0 , dmar_disabled = 1,启动参数加入intel_iommu=on 使其为0,可启动iommu */


/*
 * This domain is a statically identity mapping domain.
 *	1. This domain creats a static 1:1 mapping to all usable memory.
 * 	2. It maps to each iommu if successful.
 *	3. Each iommu mapps to this domain if successful.
 */
static struct dmar_domain *si_domain;
static int hw_pass_through = 1;    /* 实际也为 1 */


/*
 * This variable becomes 1 if iommu=pt is passed on the kernel command line.
 * If this variable is 1, IOMMU implementations do no DMA translation for
 * devices and allow every device to access to whole physical memory. This is
 * useful if a user wants to use an IOMMU only for KVM device assignment to
 * guests and not for driver dma translation.
 */
/*
 * 如果这个开了,那么所有设备都共用si_domain,我觉得可以打开这个,然后用vfio给透传的弄一个domain,
 * 这样的话,所有物理机共用一个domain,每个虚机按照group去使用domain。这样给用户的感觉就是物理机好像
 * 没有使用iommu,而确实现了device passthrough。具体怎么实现的后面代码会讲到
 */
int iommu_pass_through __read_mostly;

static int intel_iommu_strict;   /* 为1立即释放,性能较低。为0的话,会批量释放,配合timer使用 */

#define IDENTMAP_ALL		1
#define IDENTMAP_GFX		2
#define IDENTMAP_AZALIA		4  

/* 如果开启iommu_pass_through, iommu_identity_mapping = IDENTMAP_ALL */
static int iommu_identity_mapping; 

下面结合代码看下dma地址分配实现来了解下iommu如何工作

dma_alloc_coherent
-->dma_alloc_attrs
   -->dma_ops->alloc  
   /* 这个dma_ops就非常重要了,他直接决定了用哪种方式执行分配,如果没有开启iommu,则采取nommu_dma_ops,如果开启了iommu,采用intel_dma_ops。下面主要讲述intel_dma_ops */
      -->intel_alloc_coherent
         -->iommu_no_mapping  
            /*判断iommu是否map,1)dev->archdata.iommu == DUMMY_DEVICE_DOMAIN_INFO 不做map,2)identity_mapping不做map,如果满足上面两种情况,那么get_page flag的GFP_DMA相关位要置位,也就是说由于此时iommu不做map,那么申请的物理地址必须是DMA空间*/
         -->__get_free_pages  /* 获取物理页,返回虚机地址 */
         -->*dma_handle = __intel_map_single /* 这一部分是返回dma_address,这里也分两种情况 */
            -->iommu_no_mapping  /* 同上,如果不map,直接返回物理机地址,否则下面执行分配*/
            -->domain_get_iommu
            -->iova_pfn = intel_alloc_iova  /* 在device隶属domain上分配iova,这里可以理解为是除CPU和PCI外的另一个地址域,所以地址重叠与否无所谓,iommu映射是iova->pci域地址->cpu域地址  */
            -->domain_pfn_mapping   /* iommu映射的前后地址已经准备好了,这里需要实现映射  */
               -->__domain_mapping  /* 这里就是建立页表了,不在详细阐述  */
                                                      

针对iommu的工作原理这里不在过多介绍,可以参考之前文章https://blog.csdn.net/hx_op/article/details/104029622,这里先给出一张完整的数据结构图,根据图在结合上面的代码流程就能够很清晰的了解整个过程了。
在这里插入图片描述

  • struct intel_iommu是intel_iommu控制器抽象,当前机器上存在8个,每个iommu都可以完整映射工作。由于BDF(bus、device、function)共16位,所以最后一级可以有2 ^ 16 = 65536项,当然,也可以多个项填充同一个页表地址,比如iommu_pass_through置1时,那么除了透传设备,几乎全部设备都使用si_domain这个domain提供的页表项,不过当前才用的是iommu_pass_through为0的情况。struct dmar_domain 可以理解为是一个Source Identifier对应的一个页表的抽象,而struct iommu_group是具有同一个Source Identifier设备集合的抽象。
  • iommu页表建立过程有下面几个重要环节,1)iommu的root_entry,这是Source Identifier索引表;2)每个iommu_group都会有自己的Source Identifier,且和iommu_domain绑定,在vfio实现时,可以两个group共用一个domain。struct dmar_domain中包含pgd指向iommu映射页表,这个页表的基地址由domain_context_mapping函数填入到root_entry的索引表中;3)dma_alloc_coherent申请空间的时候,就会建立struct dmar_domain中pgd指向的页表了

VFIO设备透传

  • container,对应/dev/vfio/vfio设备
  • group,对应 /dev/vfio/60 设备
modprobe vfio_iommu_type1
modprobe vfio_pci
echo 1d22 3684 > /sys/bus/pci/drivers/vfio-pci/new_id  # 该操作将pci device和vfio-pci driver绑定,执行vfio_pci_probe,创建/dev/vfio下vfio_group组,创建vfio_group,设备挂到组下面
[root@szth-bcc-online-com0-1208.szth.baidu.com ~]# ls /dev/vfio/
60  69  vfio
/*
 *  先按照https://www.kernel.org/doc/Documentation/vfio.txt示例代码分析主要流程
 *  vfio_fops、vfio_group_fops分别对应/dev/vfio下vfio和group驱动,当前所需模块已加载后
 *  vfio是针对透传的通用抽象框架,vfio_pci是针对pci设备的透传实现,就是说vfio这一层,本身还支持 \
 *  非pci设备透传
 *  container 可以是vfio_group集合的抽象,与虚机对应。
 *  vfio_group是vfio层对iommu_group的抽象,与iommu_group强相关
 */
container = open("/dev/vfio/vfio", O_RDWR);  // 获取container
group = open("/dev/vfio/26", O_RDWR);        // 获取group
ioctl(group, VFIO_GROUP_SET_CONTAINER, &container); // container 加入 group
ioctl(container, VFIO_SET_IOMMU, VFIO_TYPE1_IOMMU); // 设置VFIO_TYPE1_IOMMU作为vfio driver抽象


vfio_group_set_container   
-->driver->ops->attach_group = vfio_iommu_type1_attach_group //分配 domain,
   -->domain->domain = iommu_domain_alloc(bus);  //注 ,这里又分配了一个domain
   -->iommu_attach_group(domain->domain, iommu_group);  
      -->__iommu_attach_group
         -->intel_iommu_attach_device  //如果仔细看不使用vfio的流程,也有这里,其实这个函数就是建立domain和device的关系,不过对于一个虚机来讲,会尽可能的复用domain,也就是说一个domain可以包含多个group,vfio_iommu_type1_attach_group函数后面会处理复用问题
            // 这后面就和之前过程一样了,没啥特殊的,而且vfio_cantainer和vfio_group关系也比较好梳理,就是几个简单链表,这个不做梳理
            -->domain_add_dev_info
               -->dmar_insert_one_dev_info
                  -->device_domain_info *info = alloc_devinfo_mem();
                  -->domain_attach_iommu(domain, iommu) /*domain 和 iommu关联*/
                  -->list_add(&info->link, &domain->devices)
                  -->list_add(&info->global, &device_domain_list)
                  -->dev->archdata.iommu = info
                  -->domain_context_mapping  /*这一步很重要,这里面将domain的页表地址放入了其对应的src项中  */
                  

参考网址

https://www.kernel.org/doc/Documentation/Intel-IOMMU.txt

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值