NVDIMM and acpi

Posted by Maggie on December 4, 2020

QEMU QOM中 NVDIMM设备

nvdimm_in_qom

系统发现和枚举NVDIMM设备流程

  • 首先需要在ACPI namespace的 根节点 \SB下有 NVDIMM root device

    • 该根设备的HID 为ACPI0012

      (qemu中将该设备放在了SSDT中,不是DSDT中)

  • 如果命名空间中发现了 NVDIMM设备, OS才会根据RSDP指向的NFIT表, 枚举所有的NVDIMM设备

NVDIMM相关ACPI 表 示例

nvdimm_acpi_eg

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_flow

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后将结果返回

      image-20201207172944117

  • IO端口的读写方法