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的三个地方需要额外的工作。
分别是:
添加nvdimm label区域
创建NFIT表的内容
创建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来看。
刚开始看确实有点头大,不过慢慢就好了。我稍微来解释一下。
定义函数
所以看到这个表中定义了好些函数: _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?