NVDIMM

NVDIMM是PCDIMM的子类型,所以整体的流程基本差不多,着重讲一下几个不同之处。

全局

我们先来看一下全局上,一个nvdimm设备初始化并加入系统有哪些步骤。

main()
    qemu_opts_foreach(qemu_find_opts("device"), device_init_func, NULL, NULL)
        ...
        device_set_realized()
            hotplug_handler_pre_plug
                ...
                memory_device_pre_plug
                    nvdimm_prepare_memory_region                (1)

            if (dc->realize) {
              dc->realize(dev, &local_err);
            }

            hotplug_handler_plug
                ...
                nvdimm_plug
                    nvdimm_build_fit_buffer                     (2)
    qemu_run_machine_init_done_notifiers()
        pc_machine_done
            acpi_setup
                acpi_build
                    nvdimm_build_acpi                           (3)
                        nvdimm_build_ssdt
                        nvdimm_build_nfit

                acpi_add_rom_blob(build_state, tables.table_data,
                                  "etc/acpi/tables", 0x200000);
                acpi_add_rom_blob(build_state, tables.linker->cmd_blob,
                                  "etc/table-loader", 0);

从上面的图中可以看到NVDIMM设备初始化和PCDIMM设备类似,也是有这么几个步骤

  • 插入准备

  • 实例化

  • 插入善后

  • 添加ACPI表

其中大部分的工作和PCDIMM一样,只有标注了1,2,3的三个地方需要额外的工作。

分别是:

  1. 添加nvdimm label区域

  2. 创建NFIT表的内容

  3. 创建SSDT和NFIT并加入acpi

分配label区域

按照当前的实现,使用nvdimm设备时,在分配空间的最后挖出一块做label。这个工作在nvdimm_prepare_memory_region中完成。

这块label区域在虚拟机内核中通过DSM方法来操作,后面我们会看到DSM方法是如何虚拟化的。

填写nfit信息

nfit表是用来描述nvdimm设备上地址空间组织形式的,所以这部分需要在计算好了dimm地址后进行,有nvdimm_build_fit_buffer完成。

展开这个函数我们可以看到

也就是分别构建了 SPA, MEMDEV和DCR三个信息。而这些信息就在内核函数add_table中处理。

不看具体内容,我们只看add_table中类型的定义:

上面三个类型对应了这个枚举类型的0,1,4。

构建acpi

最终nvdimm的信息要填写到acpi中,一共有两张表:SSDT和NFIT。这部分工作在nvdimm_build_acpi中完成。

其中NFIT表在nvdimm_build_nfit函数中完成,其实没有太多内容。就是将上面创建好的表内容拷贝过来。

而SSDT这张表就讲究多了,这里面涉及了acpi的一些概念。

ACPI Table

在进入代码之前,还是要先了解一下acpi的一些概念。这里并不是系统性介绍acpi,只是补充一些代码阅读中涉及的概念。

ACPI(Advanced Configuration and Power Management Interface),简单理解就是用来描述硬件配置的。

为了做好管理,acpi当然提供了很多复杂的功能,其中我们关注的就是acpi table。而这些表的作用比较直观,那就是按照一定的规范来描述硬件设备的属性。

规范中定义了很多表,而这些表按照我的理解可以分成两类:

  • DSDT & SSDT

  • 其他

DSDT & SSDT 虽然也叫表,但是他们使用AML语言编写的,可以被解析执行的。也就是这两张表是可编程的。 而其他的表则是静态的,按照标准写好的数据结构。

查看Table

在linux上可以很方便得查看acpi table。因为他们都在sysfs上。

/sys/firmware/acpi/tables/

文件的内容还需要使用工具查看。

这个命令将会把所有的acpi table的二进制形式导出到.dat文件,并保存在当前目录下。

比如我们就看nvdimm中创建的NFIT表,那么再执行

就能解析成可阅读的文件了。在我的虚拟机上显示如下,而且正好就是spa, memdev, dcr三个部分。

AML & ASL

开始我们也说了,acpi table分成两类。刚才的facs属于第二类,也就是静态的数据结构。

而对于第一种表,他们则复杂得多,是用AML编写的。

  • AML: ACPI Machine Language

  • ASL: ACPI source language

所以前者相当于机器语言,而后者相当于汇编。

解析这两张表的方式和刚才的一样也是通过iasl命令,但是样子就完全不一样了。我们就直接拿nvdimm构造ssdt来看。

NVDIMM SSDT

刚开始看确实有点头大,不过慢慢就好了。我稍微来解释一下。

定义函数

所以看到这个表中定义了好些函数: _DSM, RFIT, _FIT 。

值得注意的是以_ 开头的函数是在规范中定义的,而其他的则是可以自己添加的。

定义变量

如果说函数定义还比较好理解,那么变量的定义就有点不那么直观了。

最简单的方式是赋值:

当然这个变量Local6是自定义的,只要直接使用就好。

另外一种就是比较特殊的,我称之为指针方式。因为看着和c语言的指针访问很像。而且这种方式需要做两步。

  • 定义Region

  • 定义Field

先定义出一块空间,里面包含了这块空间对应的起始地址和长度。

然后定义这块空间的field,这点上看和c语言的结构体很像。

这样相当于得到了一个指针变量NFIT,这个地址是指向0x0A18的4个字节。

最后访问这个区域就是直接用赋值语句:

就相当于往NTFI这个指针写了Local6的值。

模拟nvdimm的_DSM

好了,绕了这么大一圈,终于可以回到正题看看nvdimm的_DSM是如何模拟的。

所谓模拟,也就是在guest执行_DSM方法时截获该操作,由qemu进行模拟。

guest内核

所以正式开始前,我们还得看一眼内核中执行_DSM时做了点什么。

下面的代码截取自acpi_evaluate_dsm()

别的不管,主要看一共传进来了四个参数。比如有版本号和函数。这里先记着,留着后面再来看。

AML

回到之前看得acpi表总的来说,SSDT表在地址空间上构建了这么两段:

其目的就是创建了一块共享内存,用来在guest和qemu之间传递模拟_DSM的参数和返回值。

比如我们可以在aml文件中看到

而其中Arg1就是内核传递下来的参数rev,Arg2是内核传递参数的func。

Qemu

当AML执行了 NFIT = Local6 的时候,接下去的操作就被Qemu截获了。而这个Local6参数的值是什么呢?

Local6 = MEMA

所以,实际上就是告诉了Qemu一块共享内存的空间。这下是不是明白了?

那Qemu截获后是如何操作?这还要看nvdimm_dsm_ops了。因为是写操作,所以只看write方法。

其中有一个变量很引人注目:NvdimmDsmIn。

而这个变量类型定义为:

这下是不是和AML中共享内存的结构对应起来了?

好了,再详细的细节就和具体的函数实现相关了。留给大家自己去探索把~

最后的疑问

如果大家仔细看,比较qemu中aml的代码和guest中SSDT生成的文件,其中还有一个问题没有解决。

那就是 Local6 = MEMA 的这个MEMA的地址是如何得到的。

这个就牵扯到了另外两个超纲的知识点,将在FW_CFG章节中解开这个谜团。

Last updated

Was this helpful?