3. MSI WMI 平台特性驱动 (msi-wmi-platform)

3.1. 简介

许多 MSI 笔记本支持各种功能,例如读取风扇传感器。这些功能由嵌入式控制器控制,ACPI 固件在嵌入式控制器接口之上公开了一个标准的 ACPI WMI 接口。

3.2. WMI 接口描述

可以使用 bmfdec 实用程序从嵌入式二进制 MOF (bmof) 数据中解码 WMI 接口描述

[WMI, Locale("MS\0x409"),
 Description("This class contains the definition of the package used in other classes"),
 guid("{ABBC0F60-8EA1-11d1-00A0-C90629100000}")]
class Package {
  [WmiDataId(1), read, write, Description("16 bytes of data")] uint8 Bytes[16];
};

[WMI, Locale("MS\0x409"),
 Description("This class contains the definition of the package used in other classes"),
 guid("{ABBC0F63-8EA1-11d1-00A0-C90629100000}")]
class Package_32 {
  [WmiDataId(1), read, write, Description("32 bytes of data")] uint8 Bytes[32];
};

[WMI, Dynamic, Provider("WmiProv"), Locale("MS\0x409"),
 Description("Class used to operate methods on a package"),
 guid("{ABBC0F6E-8EA1-11d1-00A0-C90629100000}")]
class MSI_ACPI {
  [key, read] string InstanceName;
  [read] boolean Active;

  [WmiMethodId(1), Implemented, read, write, Description("Return the contents of a package")]
  void GetPackage([out, id(0)] Package Data);

  [WmiMethodId(2), Implemented, read, write, Description("Set the contents of a package")]
  void SetPackage([in, id(0)] Package Data);

  [WmiMethodId(3), Implemented, read, write, Description("Return the contents of a package")]
  void Get_EC([out, id(0)] Package_32 Data);

  [WmiMethodId(4), Implemented, read, write, Description("Set the contents of a package")]
  void Set_EC([in, id(0)] Package_32 Data);

  [WmiMethodId(5), Implemented, read, write, Description("Return the contents of a package")]
  void Get_BIOS([in, out, id(0)] Package_32 Data);

  [WmiMethodId(6), Implemented, read, write, Description("Set the contents of a package")]
  void Set_BIOS([in, out, id(0)] Package_32 Data);

  [WmiMethodId(7), Implemented, read, write, Description("Return the contents of a package")]
  void Get_SMBUS([in, out, id(0)] Package_32 Data);

  [WmiMethodId(8), Implemented, read, write, Description("Set the contents of a package")]
  void Set_SMBUS([in, out, id(0)] Package_32 Data);

  [WmiMethodId(9), Implemented, read, write, Description("Return the contents of a package")]
  void Get_MasterBattery([in, out, id(0)] Package_32 Data);

  [WmiMethodId(10), Implemented, read, write, Description("Set the contents of a package")]
  void Set_MasterBattery([in, out, id(0)] Package_32 Data);

  [WmiMethodId(11), Implemented, read, write, Description("Return the contents of a package")]
  void Get_SlaveBattery([in, out, id(0)] Package_32 Data);

  [WmiMethodId(12), Implemented, read, write, Description("Set the contents of a package")]
  void Set_SlaveBattery([in, out, id(0)] Package_32 Data);

  [WmiMethodId(13), Implemented, read, write, Description("Return the contents of a package")]
  void Get_Temperature([in, out, id(0)] Package_32 Data);

  [WmiMethodId(14), Implemented, read, write, Description("Set the contents of a package")]
  void Set_Temperature([in, out, id(0)] Package_32 Data);

  [WmiMethodId(15), Implemented, read, write, Description("Return the contents of a package")]
  void Get_Thermal([in, out, id(0)] Package_32 Data);

  [WmiMethodId(16), Implemented, read, write, Description("Set the contents of a package")]
  void Set_Thermal([in, out, id(0)] Package_32 Data);

  [WmiMethodId(17), Implemented, read, write, Description("Return the contents of a package")]
  void Get_Fan([in, out, id(0)] Package_32 Data);

  [WmiMethodId(18), Implemented, read, write, Description("Set the contents of a package")]
  void Set_Fan([in, out, id(0)] Package_32 Data);

  [WmiMethodId(19), Implemented, read, write, Description("Return the contents of a package")]
  void Get_Device([in, out, id(0)] Package_32 Data);

  [WmiMethodId(20), Implemented, read, write, Description("Set the contents of a package")]
  void Set_Device([in, out, id(0)] Package_32 Data);

  [WmiMethodId(21), Implemented, read, write, Description("Return the contents of a package")]
  void Get_Power([in, out, id(0)] Package_32 Data);

  [WmiMethodId(22), Implemented, read, write, Description("Set the contents of a package")]
  void Set_Power([in, out, id(0)] Package_32 Data);

  [WmiMethodId(23), Implemented, read, write, Description("Return the contents of a package")]
  void Get_Debug([in, out, id(0)] Package_32 Data);

  [WmiMethodId(24), Implemented, read, write, Description("Set the contents of a package")]
  void Set_Debug([in, out, id(0)] Package_32 Data);

  [WmiMethodId(25), Implemented, read, write, Description("Return the contents of a package")]
  void Get_AP([in, out, id(0)] Package_32 Data);

  [WmiMethodId(26), Implemented, read, write, Description("Set the contents of a package")]
  void Set_AP([in, out, id(0)] Package_32 Data);

  [WmiMethodId(27), Implemented, read, write, Description("Return the contents of a package")]
  void Get_Data([in, out, id(0)] Package_32 Data);

  [WmiMethodId(28), Implemented, read, write, Description("Set the contents of a package")]
  void Set_Data([in, out, id(0)] Package_32 Data);

  [WmiMethodId(29), Implemented, read, write, Description("Return the contents of a package")]
  void Get_WMI([out, id(0)] Package_32 Data);
};

由于 Windows 处理 CreateByteField() ACPI 操作符的方式的特殊性(仅在最终访问无效的字节字段时才会发生错误),所有方法都需要一个 32 字节的输入缓冲区,即使二进制 MOF 中另有说明。

输入缓冲区包含一个字节来选择要访问的子功能和 31 个字节的输入数据,其含义取决于正在访问的子功能。

输出缓冲区包含一个字节,表示成功或失败(失败时为 0x00)和 31 个字节的输出数据,其含义取决于正在访问的子功能。

3.2.1. WMI 方法 Get_EC()

返回嵌入式控制器信息,选择的子功能无关紧要。输出数据包含一个标志字节和一个 28 字节的控制器固件版本字符串。

标志字节的前 4 位包含嵌入式控制器接口的次要版本,接下来的 2 位包含嵌入式控制器接口的主要版本。

第 7 位表示嵌入式控制器页面是否已更改(确切含义未知),最后一位表示该平台是否为 Tigerlake 平台。

MSI 软件似乎仅在最后一位设置时才使用此接口。

3.2.2. WMI 方法 Get_Fan()

可以通过选择子功能 0x00 来访问风扇转速传感器。输出数据包含最多四个以大端格式排列的 16 位风扇转速读数。大多数机器不支持所有四个风扇转速传感器,因此剩余的读数硬编码为 0x0000

风扇 RPM 读数可以使用以下公式计算

RPM = 480000 / <风扇转速读数>

如果风扇转速读数为零,则风扇 RPM 也为零。

3.2.3. WMI 方法 Get_WMI()

返回 ACPI WMI 接口的版本,选择的子功能无关紧要。输出数据包含两个字节,第一个字节包含主版本,最后一个字节包含 ACPI WMI 接口的次要修订版本。

MSI 软件似乎仅在主版本大于 2 时才使用此接口。

3.3. 逆向工程 MSI WMI 平台接口

警告

随意探查嵌入式控制器接口可能会对机器造成损坏和其他不良副作用,请小心操作。

底层嵌入式控制器接口由 msi-ec 驱动程序使用,并且似乎许多方法只是将嵌入式控制器内存的一部分复制到输出缓冲区中。

这意味着可以通过查看 ACPI AML 代码访问嵌入式控制器内存的哪个部分来逆向工程剩余的 WMI 方法。该驱动程序还支持用于直接执行 WMI 方法的 debugfs 接口。此外,可以通过使用 force=true 加载模块来禁用有关不支持的硬件的任何安全检查。

有关 MSI 嵌入式控制器接口的更多信息,请参见 msi-ec 项目

特别感谢 github 用户 glpnk 展示如何解码风扇转速读数。