QEMU QOM中 NVDIMM设备
系统发现和枚举NVDIMM设备流程
-
首先需要在ACPI namespace的 根节点
\SB
下有 NVDIMM root device-
该根设备的HID 为
ACPI0012
(qemu中将该设备放在了SSDT中,不是DSDT中)
-
-
如果命名空间中发现了 NVDIMM设备, OS才会根据RSDP指向的NFIT表, 枚举所有的NVDIMM设备
NVDIMM相关ACPI 表 示例
qemu中NVDIMM 的ACPI表
配置
1
2
3
// qemu 命令行配置NVDIMM设备
-object memory-backend-file,id=mem1,size=4G,mem-path=/dev/hugepage,
-device nv-dimm,id=nvdimm1,memdev=mem1
-
NUMA信息通过ACPI SRAT表传递给guest
-
NVDIMM设备通过 以下设置传递给guest
- ACPI NFIT表
- DSDT中 NVDIMM root device
- 相应的 memory device 通过PNP0C80设备传递给 ACPI (TODO:function
build_memory_hotplug_aml
)
代码调用流程
NVDIMM 相关ACPI表详细分析
/*
* Intel ACPI Component Architecture
* AML/ASL+ Disassembler version 20180105 (64-bit version)
* Copyright (c) 2000 - 2018 Intel Corporation
*
* Disassembling to symbolic ASL+ operators
*
* Disassembly of SSDT.dat, Tue Oct 9 23:58:03 2018
*
* Original Table Header:
* Signature "SSDT"
* Length 0x000002CD (717)
* Revision 0x01
* Checksum 0x44
* OEM ID "BOCHS "
* OEM Table ID "NVDIMM"
* OEM Revision 0x00000001 (1)
* Compiler ID "BXPC"
* Compiler Version 0x00000001 (1)
*/
DefinitionBlock ("", "SSDT", 1, "BOCHS ", "NVDIMM", 0x00000001)
{
Scope (\_SB)
{
Device (NVDR) // NVDIMM Root Device
{
Name (_HID, "ACPI0012" ) // _HID: Hardware ID
Method (NCAL, 5, Serialized)
{
Local6 = MEMA /* \MEMA */
OperationRegion (NPIO, SystemIO, 0x0A18, 0x04)
OperationRegion (NRAM, SystemMemory, Local6, 0x1000)
Field (NPIO, DWordAcc, NoLock, Preserve)
{
NTFI, 32 // NTFI = Local6 = MEMA, 通知qemu 已经将 input 写好了
}
Field (NRAM, DWordAcc, NoLock, Preserve) // DSM input
{
HDLE, 32,
REVS, 32, // reversion id, Arg1
FUNC, 32, // Function index, Arg2
FARG, 32672 // 4084 bytes, The Arg3 for _DSM method.
}
Field (NRAM, DWordAcc, NoLock, Preserve) // DSM output
{
RLEN, 32,
ODAT, 32736
}
If ((Arg4 == Zero)) // UUID for NVDIMM Root Device
{
Local0 = ToUUID ("2f10e7a4-9e91-11e4-89d3-123b93f75cba")
}
ElseIf ((Arg4 == 0x00010000)) // _FIT 走到这个分支 NVDIMM_QEMU_RSVD_HANDLE_ROOT
{
Local0 = ToUUID ("648b9cf2-cda1-4312-8ad9-49c4af32bd62")
}
Else // NVDIMM devices
{
Local0 = ToUUID ("4309ac30-0d11-11e4-9191-0800200c9a66")
}
If (((Local6 == Zero) | (Arg0 != Local0)))
{
If ((Arg2 == Zero)) // query what functions are supported
{
Return (Buffer (One)
{
0x00 // .
})
}
Return (Buffer (One)
{
0x01 // .
})
}
HDLE = Arg4 // 0x10000 is qemu internal function index
REVS = Arg1 // -> One
FUNC = Arg2 // -> One
If (((ObjectType (Arg3) == 0x04) & (SizeOf (Arg3) == One))) // _FIT 会走到这个分支: 长度为1 的package类型
{
Local2 = Arg3 [Zero] // 从package中得到 OFFSET
Local3 = DerefOf (Local2)
FARG = Local3
}
NTFI = Local6
Local1 = (RLEN - 0x04)
Local1 = (Local1 << 0x03)
CreateField (ODAT, Zero, Local1, OBUF)
Concatenate (Buffer (Zero){}, OBUF, Local7)
Return (Local7)
}
Method (_DSM, 4, NotSerialized) // _DSM: Device-Specific Method
{
Return (NCAL (Arg0, Arg1, Arg2, Arg3, Zero))
}
Name (RSTA, Zero)
Method (RFIT, 1, Serialized) // _FIT 的辅助方法
{
Name (OFST, Zero)
OFST = Arg0
Local0 = NCAL (ToUUID ("648b9cf2-cda1-4312-8ad9-49c4af32bd62"), One, One, Package (0x01)
{
OFST
}, 0x00010000)
CreateDWordField (Local0, Zero, STAU)
RSTA = STAU /* \_SB_.NVDR.RFIT.STAU */
If ((Zero != STAU))
{
Return (Buffer (Zero){})
}
Local1 = SizeOf (Local0)
Local1 -= 0x04
If ((Local1 == Zero)) // 已经读到结尾
{
Return (Buffer (Zero){})
}
CreateField (Local0, 0x20, (Local1 << 0x03), BUFF)
Return (BUFF) /* \_SB_.NVDR.RFIT.BUFF */
}
Method (_FIT, 0, Serialized) // _FIT: Firmware Interface Table
{
Local2 = Buffer (Zero){}
Local3 = Zero // OFFSET
While (One)
{
Local0 = RFIT (Local3)
Local1 = SizeOf (Local0)
If ((RSTA == 0x0100)) // 0x100表示FIT changed, 应该重新开始扫描
{
Local2 = Buffer (Zero){}
Local3 = Zero
}
Else
{
If ((Local1 == Zero)) // 没有数据可读了
{
Return (Local2)
}
Local3 += Local1
Concatenate (Local2, Local0, Local2) // 都会先放在 Local2中
}
}
}
Device (NV00)
{
Name (_ADR, One) // _ADR: Address
Method (_DSM, 4, NotSerialized) // _DSM: Device-Specific Method
{
Return (NCAL (Arg0, Arg1, Arg2, Arg3, One))
}
}
Device (NV01)
{
Name (_ADR, 0x02) // _ADR: Address
Method (_DSM, 4, NotSerialized) // _DSM: Device-Specific Method
{
Return (NCAL (Arg0, Arg1, Arg2, Arg3, 0x02))
}
}
Device (NV02)
{
Name (_ADR, 0x03) // _ADR: Address
Method (_DSM, 4, NotSerialized) // _DSM: Device-Specific Method
{
Return (NCAL (Arg0, Arg1, Arg2, Arg3, 0x03))
}
}
Device (NV03)
{
Name (_ADR, 0x04) // _ADR: Address
Method (_DSM, 4, NotSerialized) // _DSM: Device-Specific Method
{
Return (NCAL (Arg0, Arg1, Arg2, Arg3, 0x04))
}
}
}
}
Name (MEMA, 0xBFFFE000)
}
-
ACPI中跟NVDIMM相关的 有NVDIMM root device, 该设备允许OS发现 NVDIMM硬件设备, 对应方法为
_FIT
方法。 -
NVDR
是 NVDIMM root deivce, 该设备中的NCAL
方法和RFIT
方法都是公共方法的抽取, 其中调用关系如下1 2 3 4 5
// NVDIMM root deivce (NVDR) -> _FIT -> RFIT -> NCAL ^ // 非root NVDIMM 设备 | (NV0x deivce) -> _DSM ------------|
可见
RFIT
方法只是_FIT
方法的辅助方法。为什么要定义这个辅助方法?
因为
NCA
只是根据NvdimmDsmIn
数据请求, 返回相应的结果但是
_FIT
需要返回所有的FIT NVDIMM
数据, 需要有一个辅助函数来进行 状态判断 和 结果处理-
NCAL
方法分析(实际上所有的DSM逻辑都在这个函数中)- 构造DSM In Data 和 DSM Out data
- 在内存中中写入 DSM In Data请求
- 写IO端口
0x0a18
,陷出到qemu - qemu中 调用该 IO region的写方法
- 处理完请求后,将 DSM Out Data结果写到内存中
- 陷入: 系统ACPI层 继续向下执行
NCAL
方法, 即: 简单处理 DSM Out Data后将结果返回
-
-
IO端口的读写方法