整体流程图

长图预警!!!
下图完整展示了Intel IOMMU的初始化流程,是对本文所有内容的总结。只要看懂这张图,读者就能够完全理解Intel IOMMU的初始化流程。
接下来,笔者将按流程图的顺序,结合代码,介绍Intel IOMMU初始化流程的一些关键步骤。图中部分细节,本文可能并未提到,读者可结合代码自行理解。

Intel IOMMU初始化前的准备工作

函数调用树

start_kernel ->mm_init ->mem_init ->pci_iommu_alloc ->pci_swiotlb_init ->swiotlb_initdetect_intel_iommu

其中的核心函数是pci_iommu_alloc()。

pci_iommu_alloc()

下面展示该函数的代码。其中,for循环内被注释的部分是原始代码,而为了调试方便,笔者对代码略作改动,在不改变其逻辑的前提下,输出一些关键信息。注:printk()函数中的格式控制字符串"%ps",能够输出函数指针所指向的函数名的字符串。

void __init pci_iommu_alloc(void)
{struct iommu_table_entry *p;sort_iommu_table(__iommu_table, __iommu_table_end);check_iommu_entries(__iommu_table, __iommu_table_end);for (p = __iommu_table; p < __iommu_table_end; p++) {/*if (p && p->detect && p->detect() > 0) {p->flags |= IOMMU_DETECTED;if (p->early_init)p->early_init();if (p->flags & IOMMU_FINISH_IF_DETECTED)break;}*/if (p && p->detect) {int p_detect_ret = p->detect();printk("[sh-debug] In pci_iommu_alloc(), p is %ps(); p->detect is %ps(); %ps() returns %d.",p, p->detect, p->detect, p_detect_ret);if (p_detect_ret > 0) {p->flags |= IOMMU_DETECTED;printk("[sh-debug] In pci_iommu_alloc(), p->early_init is %ps().", p->early_init);if (p->early_init)p->early_init();if (p->flags & IOMMU_FINISH_IF_DETECTED)break;}}}
}

首先看for循环之前的代码。在执行pci_iommu_alloc()之前,内核已经通过汇编指令,将IOMMU相关的启动函数,加载到IOMMU Table中。
那么,IOMMU Table中到底有哪些启动函数呢?
根据上一篇文章所述,为了启用Intel IOMMU,我们进行了如下配置:
在.config文件中:

CONFIG_INTEL_IOMMU=y

在启动参数文件中:

iommu=force intel_iommu=on

基于如上配置,编译并重启内核后,使用dmesg过滤输出信息:

dmesg | grep "pci_iommu_alloc"

结果如下:

在pci_iommu_alloc()代码的for循环中,p每次指向一个IOMMU table entry,而每个entry包含detect和early_init两个函数指针(其实还有第三个函数指针,late_init,在本文末尾介绍pci_iommu_init()函数时会涉及到)。pci_iommu_alloc()会先调用p->detect,只有当该函数返回值大于0时,才会调用p->early_init。
那么,哪些detect函数的返回值是正数,从而会调用对应的early_init函数呢?在上图中,只有两个。

  1. p->detect = pci_swiotlb_detect_4gb。该detect函数返回1,之后调用对应的early_init函数,pci_swiotlb_init(),用于初始化SWIOTLB。
  2. p->detect = detect_intel_iommu。该detect函数返回1,但并没有对应的early_init函数(看截图最后一行,p->early_init is 0x0)。

看到这里,读者想必会有两个疑问:

  1. 我们不是已经在启动参数中指定使用Intel IOMMU了吗?按照之前文章的说法,Intel IOMMU与SWIOTLB不能共存。那么这里为什么还会初始化SWIOTLB?
  2. 既然detect_intel_iommu()没有对应的early_init函数,那么Intel IOMMU的初始化函数,是如何被调用的呢?

对于这两个疑问,接下来我们逐一解答。

为什么会初始化SWIOTLB

对于第一个问题,我们需要看SWIOTLB的detect函数——pci_swiotlb_detect_4gb()代码。

/** If 4GB or more detected (and iommu=off not set) or if SME is active* then set swiotlb to 1 and return 1.*/
int __init pci_swiotlb_detect_4gb(void)
{/* don't initialize swiotlb if iommu=off (no_iommu=1) */if (!no_iommu && max_possible_pfn > MAX_DMA32_PFN)swiotlb = 1;/** If SME is active then swiotlb will be set to 1 so that bounce* buffers are allocated and used for devices that do not support* the addressing range required for the encryption mask.*/if (sme_active())swiotlb = 1;return swiotlb;
}

需要说明的是,在上述代码中,swiotlb是一个全局变量,它决定了SWIOTLB是否被初始化。相关代码很简单:

void __init pci_swiotlb_init(void)
{if (swiotlb)swiotlb_init(0);
}

可见,只有swiotlb = 1时,SWIOTLB初始化函数swiotlb_init()才会被调用——这个函数在本系列第二篇中进行过详细介绍。在调用pci_swiotlb_init()之前,有若干函数可能会将swiotlb的值置为1,上面提到的pci_swiotlb_detect_4gb()便是其中之一。
我们只需关注pci_swiotlb_detect_4gb()的第一个if语句:

if (!no_iommu && max_possible_pfn > MAX_DMA32_PFN)swiotlb = 1;

对于第一个子条件,顾名思义,!no_iommu显然为true(我们已经启用了Intel IOMMU,显然不是No IOMMU)。重点解释一下第二个子条件。MAX_DMA32_PFN是32位设备能够寻址到的最大页数。所以,如果“物理内存中的页数 > MAX_DMA32_PFN”,则将swiotlb置为1。
对于条件“物理内存中的页数 > MAX_DMA32_PFN”,我们把不等式两边同时乘以PAGE_SIZE(页的大小),这个条件实际上等价于:“物理内存 > 32位设备能够寻址的最大内存”。
而我们知道,32位设备能够寻址的最大内存为232 = 4GB。因此,这个判断条件最终转换为:
“物理内存 > 4GB”
现在就非常明确了:如果物理内存大于4GB,那么pci_swiotlb_detect_4gb()就会将swiotlb置为1,从而导致后续swiotlb_init()被调用,以初始化SWIOTLB。

笔者用free命令查看自己机器的可用物理内存,确实大于4GB。

free -h

作为对比测试,我们在启动参数中,新增一项"mem=1G",将可用物理内存限制为1GB。

iommu=force intel_iommu=on mem=1G

重启内核,首先用:

free -h

确认可用物理内存确实为1GB。

而后用:

dmesg | grep "pci_iommu_alloc"

查看内核日志,如下图所示。

这次我们可以看到,在可用内存不大于4GB时,detect函数pci_swiotlb_detect_4gb()返回值是0,从而不会调用early_init函数pci_swiotlb_init(),因而不会初始化SWIOTLB。

Intel IOMMU的初始化函数是如何被调用的

现在解答第二个疑问。
虽然detect函数detect_intel_iommu()对应的early_init函数是空函数,不过,detect_intel_iommu()函数会将x86_init.iommu.iommu_init设置为intel_iommu_init,后者正是Intel IOMMU的初始化函数。

int __init detect_intel_iommu(void)
{int ret;/* ...... */
#ifdef CONFIG_X86if (!ret) {/* 设置iommu_init函数为intel_iommu_init */x86_init.iommu.iommu_init = intel_iommu_init;x86_platform.iommu_shutdown = intel_iommu_shutdown;}#endif/* ...... */return ret ? ret : 1;
}

那么,intel_iommu_init()何时会被调用呢?相关的调用流程如下:

kernel_init ->kernel_init_freeable ->do_one_initcall ->pci_iommu_init ->x86_init.iommu.iommu_init    /* i.e. intel_iommu_init */

以下展示pci_iommu_init()的代码。此处调用x86_init.iommu.iommu_init(),实际上就是调用intel_iommu_init()。

static int __init pci_iommu_init(void)
{struct iommu_table_entry *p;x86_init.iommu.iommu_init();for (p = __iommu_table; p < __iommu_table_end; p++) {if (p && (p->flags & IOMMU_DETECTED) && p->late_init)p->late_init();}return 0;
}

不能共存?

讲到这里,细心的读者会发现,笔者还是没有回答“Intel IOMMU与SWIOTLB不能共存”这一疑问——根据上述分析,如果物理内存大于4GB,那么SWIOTLB就会被初始化;而根据我们配置的启动参数,Intel IOMMU也会被初始化。既然二者都被初始化,那它们不就共存了吗?
这时,我们就要引用一句古话:“一山难容二虎”。聪明的读者应该能立即理解此言的含义。不理解也没关系,请看后续章节的分析。

intel_iommu_init()的主要工作

intel_iommu_init()是Intel IOMMU的初始化函数,其主要的函数调用树如下:

intel_iommu_init ->init_dmars ->init g_iommusintel_iommu_enable_qi ->dmar_enable_qiiommu_init_domainsiommu_alloc_root_entrySET swiotlb = 0

这个函数完成了Intel IOMMU所必需数据结构的初始化工作,本文对此不展开介绍。重点关注最后一行:“SET swiotlb = 0”。这不是一个函数名,而只是一个行为:将全局变量swiotlb置为0。以下展示相关代码,非常简单:

int __init intel_iommu_init(void)
{/* ...... */
#if defined(CONFIG_X86) && defined(CONFIG_SWIOTLB)/** If the system has no untrusted device or the user has decided* to disable the bounce page mechanisms, we don't need swiotlb.* Mark this and the pre-allocated bounce pages will be released* later.*/if (!has_untrusted_dev() || intel_no_bounce)swiotlb = 0;
#endif/* ...... */
}

我们看到,如果.config文件中CONFIG_X86和CONFIG_SWIOTLB都为y(在我们的实验机器上确实如此),那么这个if判断就会执行。结合代码与注释,可以得知:如果系统没有检测到不可信设备(untrusted device),或者全局变量intel_no_bounce为1,那么swiotlb就会被置为0。
那么,这两个条件是否成立呢?
一般情况下,系统并不会加载不可信设备。在我们的实验机器上也是如此。因此,第一个条件是成立的。由于这个if语句是条件或,所以直接返回true,"swiotlb = 0"会被执行。
至于intel_no_bounce,我们也顺带介绍一下。它是一个全局变量,默认值为0,代表Intel IOMMU会用到bounce buffer(就是先前文章提到的SWIOTLB bounce机制)。除非在启动参数中进行如下配置,才会将其置为1,代表禁用bounce buffer机制:

intel_iommu=nobounce

我们并没有进行如此配置,所以它等于默认值0。因此,如果对第二个条件进行判断,那么会返回false——当然,根据if语句的短路原则,在第一个条件返回true的情况下,第二个条件根本不会进行判断。

释放已分配的SWIOTLB Buffer和SWIOTLB管理数据结构

是时候水到渠成地解释“一山难容二虎”的含义了。
上一节我们讲到,一般情况下,Intel IOMMU初始化过程中,也就是函数intel_iommu_init()函数体内,会将全局变量swiotlb置为0。前面我们已经介绍过swiotlb的作用——如果它为0,那么内核后续就不会调用swiotlb_init(),从而不会初始化SWIOTLB。可是,假如内存大于4GB,那么现在SWIOTLB都已经初始化完成,此时再将swiotlb置为0,岂不是为时已晚?

答案就在如下函数调用流程中。

pci_iommu_init ->intel_iommu_init /* SET swiotlb = 0 */pci_swiotlb_late_init ->   /* If swiotlb == 0, invoke swiotlb_exit() */swiotlb_exit

再次看pci_iommu_init()函数代码:

static int __init pci_iommu_init(void)
{struct iommu_table_entry *p;x86_init.iommu.iommu_init();for (p = __iommu_table; p < __iommu_table_end; p++) {if (p && (p->flags & IOMMU_DETECTED) && p->late_init)p->late_init();}return 0;
}

该函数在for循环中,会遍历IOMMU table entry,调用对应的p->late_init函数。SWIOTLB对应的late_init函数为pci_swiotlb_late_init(),其代码如下:

void __init pci_swiotlb_late_init(void)
{/* An IOMMU turned us off. */if (!swiotlb)swiotlb_exit();else {printk(KERN_INFO "PCI-DMA: ""Using software bounce buffering for IO (SWIOTLB)\n");swiotlb_print_info();}
}

很明显,当swiotlb为0时,函数swiotlb_exit()将会被调用。以下展示swiotlb_exit()的代码,它释放了已分配的SWIOTLB Buffer和所有SWIOTLB管理数据结构。

void __init swiotlb_exit(void)
{if (!io_tlb_orig_addr)return;if (late_alloc) {free_pages((unsigned long)io_tlb_orig_addr,get_order(io_tlb_nslabs * sizeof(phys_addr_t)));free_pages((unsigned long)io_tlb_list, get_order(io_tlb_nslabs *sizeof(int)));free_pages((unsigned long)phys_to_virt(io_tlb_start),get_order(io_tlb_nslabs << IO_TLB_SHIFT));} else {memblock_free_late(__pa(io_tlb_orig_addr),PAGE_ALIGN(io_tlb_nslabs * sizeof(phys_addr_t)));memblock_free_late(__pa(io_tlb_list),PAGE_ALIGN(io_tlb_nslabs * sizeof(int)));memblock_free_late(io_tlb_start,PAGE_ALIGN(io_tlb_nslabs << IO_TLB_SHIFT));}swiotlb_cleanup();
}

如此,SWIOTLB便不复存在,只剩下Intel IOMMU。

总结

我们用一张简单的流程图,描述Intel IOMMU初始化流程的主要步骤。实际上,这5个步骤,也正好对应本文开头流程图中用红色花括号和文字标注的内容。

Linux x86-64 IOMMU详解(五)——Intel IOMMU初始化流程相关推荐

  1. linux目录档案权限详解,五、Linux的档案权限与目录配置

    1.使用者与群组 1.1.档案拥有者 1.2.群组概念 1.3.Linux系统中,默认情况下,所有的系统上的账号与一般身份使用者,还有那个root的相关信息,都是记录在/etc/passwd这个档案内 ...

  2. Linux 进程间通讯详解五

    msgrcv函数 ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg); --功能:是从一个消息队列接 ...

  3. Linux字符设备驱动详解七(“插件“设备树实现RGB灯驱动)

    文章目录 系列文章目录 前言 正文 Device Tree Overlays:"插件"设备树 传统设备树 "插件"设备树 使用前提 案例说明 设备树:foo.d ...

  4. Linux字符设备驱动详解四(使用自属的xbus驱动总线)

    文章目录 系列文章目录 前言 驱动目录 正文 驱动总线 总线管理 总线注册 设备注册 驱动注册 代码示例 总结 系列文章目录 Linux字符设备驱动详解 Linux字符设备驱动详解二(使用设备驱动模型 ...

  5. 【正点原子Linux连载】第三十五章 Linux内核顶层Makefile详解 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

  6. 《Linux设备驱动开发详解 A》一一2.3 接口与总线

    本节书摘来华章计算机出版社<Linux设备驱动开发详解 A>一书中的第2章,第2.3节,作者:宋宝华 更多章节内容可以访问云栖社区"华章计算机"公众号查看.1 2.3 ...

  7. linux看不到进程管理,关于Linux下进程的详解【进程查看与管理】

    关于Linux下进程的详解[进程查看与管理] 一.关于进程 进程: 已经启动的可执行程序的运行实力 进程的组成:一个进程包含内核中的一部分地址空间和一系列数据结构.其中地址空间是内核标记的一部分内存以 ...

  8. linux命令之-dmesg详解

    Linux命令dmesg用法详解 功能说明:显示开机信息.  语 法:dmesg [-cn][-s <缓冲区大小>]  补充说明:kernel会将开机信息存储在ring buffer中.您 ...

  9. linux 设备驱动 ppt,linux设备驱动开发详解讲座ppt

    PPT内容 这是linux设备驱动开发详解讲座ppt下载,主要介绍了设备驱动简介:建立和运行模块:字符驱动:调试技术:并发和竞争:分配内存:硬件通讯:中断处理:块设备驱动,欢迎点击下载. 嵌入式Lin ...

  10. linux开机启动服务详解

    http://blog.163.com/sjt_linux/blog/static/19931031920126295452884/ linux开机启动服务详解(转) 2012-07-29 17:45 ...

最新文章

  1. 全网首发 | 第一个opencv_contrib扩展模块中文教程限时领取
  2. 16. Logging 模块的配置与使用
  3. Android TabHost中切換、修改需要显示的Activity
  4. Python基础知识(第三天)
  5. boost::hana::common用法的测试程序
  6. 自定义SpringBoot start 被依赖时 程序包不存在的问题
  7. 测试必备:单元测试测试类,以帮助匿名管理调查的类为例详解--白盒测试
  8. 为什么修电脑的叫自己不要杀毒和清理垃圾?
  9. mysql服务器io等待高定位与分析
  10. Linux下maven安装
  11. DHCP八种报文详解
  12. 点击reset按钮失效,不能清空输入框。
  13. 百度快速排名 24小时进前五 刷网站排名
  14. 个人社区(博客)项目Romantic——功能模块划分
  15. windows 7 旗舰版下,安装vs2010旗舰版终于成功!
  16. 【Autoware】之ndt_mapping理论公式及代码对比
  17. mysql 50个经典语句_MYSQL经典语句大全——技巧篇
  18. Vray和PS的冬景效果图制作教程蓝海创意云渲染
  19. 计算机wps是什么意思啊,路由器WPS是什么意思?
  20. Efficientnet笔记:各个框架最适合的图像尺寸

热门文章

  1. U盘镜像刻录(制作U盘启动盘)
  2. Spring全家桶+分布式微服务(十次方)
  3. Repast Statecharts
  4. vb连接mysql数据库报错_vb6连接mysql数据库
  5. 谭浩强 C程序设计(第五版)
  6. 最近华为笔试题(9.8)第三题
  7. js基础(2)~元素增删,属性,节点,定时器,date,事件,模块
  8. Unity VideoPlayer组件一个接一个的播放视频
  9. 25%的CPU利用率也能够让一台笔记本如此狼狈 (小红伞)
  10. 磁盘加密工具-TrueCrypt