2. Dell DDV WMI 接口驱动 (dell-wmi-ddv)

2.1. 简介

许多 2020 年之后生产的戴尔笔记本电脑都支持基于 WMI 的接口,用于检索各种系统数据,如电池温度、ePPID、诊断数据和风扇/散热传感器数据。

该接口可能被 Windows 上的 Dell Data Vault 软件使用,因此被称为 DDV。目前 dell-wmi-ddv 驱动程序支持接口的第 2 版和第 3 版,并且可以轻松添加对新接口版本的支持。

警告

该接口被戴尔视为内部接口,因此没有可用的供应商文档。所有知识都是通过试错获得的,请记住这一点。

2.2. Dell ePPID(电子部件标识)

Dell ePPID 用于唯一标识戴尔机器中的组件,包括电池。它的形式类似于 CC-PPPPPP-MMMMM-YMD-SSSS-FFF 并包含以下信息

  • 原产国家代码 (CC)。

  • 部件号,第一个字符是填充数字 (PPPPPP)。

  • 制造商标识 (MMMMM)。

  • 生产年份/月份/日期 (YMD),以 36 进制表示,Y 是年份的最后一位数字。

  • 生产序列号 (SSSS)。

  • 可选的固件版本/修订版 (FFF)。

可以使用 eppidtool python 实用程序来解码和显示此信息。

所有关于 Dell ePPID 的信息都是使用戴尔支持文档和此网站收集的。

2.3. WMI 接口描述

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

[WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("WMI Function"), guid("{8A42EA14-4F2A-FD45-6422-0087F7A7E608}")]
class DDVWmiMethodFunction {
  [key, read] string InstanceName;
  [read] boolean Active;

  [WmiMethodId(1), Implemented, read, write, Description("Return Battery Design Capacity.")] void BatteryDesignCapacity([in] uint32 arg2, [out] uint32 argr);
  [WmiMethodId(2), Implemented, read, write, Description("Return Battery Full Charge Capacity.")] void BatteryFullChargeCapacity([in] uint32 arg2, [out] uint32 argr);
  [WmiMethodId(3), Implemented, read, write, Description("Return Battery Manufacture Name.")] void BatteryManufactureName([in] uint32 arg2, [out] string argr);
  [WmiMethodId(4), Implemented, read, write, Description("Return Battery Manufacture Date.")] void BatteryManufactureDate([in] uint32 arg2, [out] uint32 argr);
  [WmiMethodId(5), Implemented, read, write, Description("Return Battery Serial Number.")] void BatterySerialNumber([in] uint32 arg2, [out] uint32 argr);
  [WmiMethodId(6), Implemented, read, write, Description("Return Battery Chemistry Value.")] void BatteryChemistryValue([in] uint32 arg2, [out] string argr);
  [WmiMethodId(7), Implemented, read, write, Description("Return Battery Temperature.")] void BatteryTemperature([in] uint32 arg2, [out] uint32 argr);
  [WmiMethodId(8), Implemented, read, write, Description("Return Battery Current.")] void BatteryCurrent([in] uint32 arg2, [out] uint32 argr);
  [WmiMethodId(9), Implemented, read, write, Description("Return Battery Voltage.")] void BatteryVoltage([in] uint32 arg2, [out] uint32 argr);
  [WmiMethodId(10), Implemented, read, write, Description("Return Battery Manufacture Access(MA code).")] void BatteryManufactureAceess([in] uint32 arg2, [out] uint32 argr);
  [WmiMethodId(11), Implemented, read, write, Description("Return Battery Relative State-Of-Charge.")] void BatteryRelativeStateOfCharge([in] uint32 arg2, [out] uint32 argr);
  [WmiMethodId(12), Implemented, read, write, Description("Return Battery Cycle Count")] void BatteryCycleCount([in] uint32 arg2, [out] uint32 argr);
  [WmiMethodId(13), Implemented, read, write, Description("Return Battery ePPID")] void BatteryePPID([in] uint32 arg2, [out] string argr);
  [WmiMethodId(14), Implemented, read, write, Description("Return Battery Raw Analytics Start")] void BatteryeRawAnalyticsStart([in] uint32 arg2, [out] uint32 argr);
  [WmiMethodId(15), Implemented, read, write, Description("Return Battery Raw Analytics")] void BatteryeRawAnalytics([in] uint32 arg2, [out] uint32 RawSize, [out, WmiSizeIs("RawSize") : ToInstance] uint8 RawData[]);
  [WmiMethodId(16), Implemented, read, write, Description("Return Battery Design Voltage.")] void BatteryDesignVoltage([in] uint32 arg2, [out] uint32 argr);
  [WmiMethodId(17), Implemented, read, write, Description("Return Battery Raw Analytics A Block")] void BatteryeRawAnalyticsABlock([in] uint32 arg2, [out] uint32 RawSize, [out, WmiSizeIs("RawSize") : ToInstance] uint8 RawData[]);
  [WmiMethodId(18), Implemented, read, write, Description("Return Version.")] void ReturnVersion([in] uint32 arg2, [out] uint32 argr);
  [WmiMethodId(32), Implemented, read, write, Description("Return Fan Sensor Information")] void FanSensorInformation([in] uint32 arg2, [out] uint32 RawSize, [out, WmiSizeIs("RawSize") : ToInstance] uint8 RawData[]);
  [WmiMethodId(34), Implemented, read, write, Description("Return Thermal Sensor Information")] void ThermalSensorInformation([in] uint32 arg2, [out] uint32 RawSize, [out, WmiSizeIs("RawSize") : ToInstance] uint8 RawData[]);
};

每个 WMI 方法都接受一个包含 32 位索引的 ACPI 缓冲区作为输入参数,当使用电池相关的 WMI 方法时,前 8 位用于指定电池。其他 WMI 方法可能会忽略此参数或以不同的方式解释它。WMI 方法输出格式各不相同

  • 如果该函数只有一个输出,则返回相应类型的 ACPI 对象

  • 如果该函数有多个输出,则返回一个包含相同顺序输出的 ACPI 包

应彻底检查输出的格式,因为在发生错误的情况下,许多方法可能会返回格式错误的数据。

许多电池相关方法的数据格式似乎基于 Smart Battery Data Specification,因此未知的电池相关方法很可能以某种方式遵循此标准。

2.3.1. WMI 方法 GetBatteryDesignCapacity()

以 mAh 为单位返回电池的设计容量,作为 u16。

2.3.2. WMI 方法 BatteryFullCharge()

以 mAh 为单位返回电池的充满容量,作为 u16。

2.3.3. WMI 方法 BatteryManufactureName()

返回电池的制造商名称,作为 ASCII 字符串。

2.3.4. WMI 方法 BatteryManufactureDate()

返回电池的生产日期,作为 u16。日期以以下方式编码

  • 位 0 到 4 包含生产日。

  • 位 5 到 8 包含生产月。

  • 位 9 到 15 包含生产年份,偏差为 1980 年。

2.3.5. WMI 方法 BatterySerialNumber()

返回电池的序列号,作为 u16。

2.3.6. WMI 方法 BatteryChemistryValue()

返回电池的化学成分,作为 ASCII 字符串。已知的值是

  • “Li-I”表示锂离子

2.3.7. WMI 方法 BatteryTemperature()

以十分之一度开尔文为单位返回电池的温度,作为 u16。

2.3.8. WMI 方法 BatteryCurrent()

以 mA 为单位返回电池的电流,作为 s16。负值表示放电。

2.3.9. WMI 方法 BatteryVoltage()

以 mV 为单位返回电池的电压,作为 u16。

2.3.10. WMI 方法 BatteryManufactureAccess()

返回电池的健康状况,作为 u16。健康状况以以下方式编码

  • 第三个半字节包含一般故障模式

  • 第四个半字节包含特定故障代码

有效的故障模式是

  • 永久性故障 (0x9)

  • 过热故障 (0xa)

  • 过电流故障 (0xb)

所有其他故障模式都被认为是正常的。

以下故障代码对永久性故障有效

  • 保险丝熔断 (0x0)

  • 电池单元不平衡 (0x1)

  • 过电压 (0x2)

  • fet 故障 (0x3)

当电池发出永久性故障信号时,应忽略故障代码的最后两位。

以下故障代码对过热故障有效

  • 充电开始时过热 (0x5)

  • 充电期间过热 (0x7)

  • 放电期间过热 (0x8)

以下故障代码对过电流故障有效

  • 充电期间过电流 (0x6)

  • 放电期间过电流 (0xb)

2.3.11. WMI 方法 BatteryRelativeStateOfCharge()

以百分比形式返回电池容量,作为 u16。

2.3.12. WMI 方法 BatteryCycleCount()

返回电池的循环计数,作为 u16。

2.3.13. WMI 方法 BatteryePPID()

返回电池的 ePPID,作为 ASCII 字符串。

2.3.14. WMI 方法 BatteryeRawAnalyticsStart()

执行电池分析并返回状态代码

  • 0x0: 成功

  • 0x1: 不支持接口

  • 0xfffffffe: 错误/超时

注意

此方法的含义仍然很大程度上未知。

2.3.15. WMI 方法 BatteryeRawAnalytics()

返回一个缓冲区,通常包含 12 个块的分析数据。这些块包含

  • 一个从 0 开始的块号 (u8)

  • 31 字节的未知数据

注意

此方法的含义仍然很大程度上未知。

2.3.16. WMI 方法 BatteryDesignVoltage()

以 mV 为单位返回电池的设计电压,作为 u16。

2.3.17. WMI 方法 BatteryeRawAnalyticsABlock()

返回一个分析数据块,索引的第二个字节用于选择块号。

自 WMI 接口版本 3 起支持!

注意

此方法的含义仍然很大程度上未知。

2.3.18. WMI 方法 ReturnVersion()

返回 WMI 接口版本,作为 u32。

2.3.19. WMI 方法 FanSensorInformation()

返回一个包含风扇传感器条目的缓冲区,以单个 0xff 结尾。这些条目包含

  • 风扇类型 (u8)

  • 风扇转速,单位为 RPM(小端 u16)

2.3.20. WMI 方法 ThermalSensorInformation()

返回一个包含热传感器条目的缓冲区,以单个 0xff 结尾。这些条目包含

  • 热类型 (u8)

  • 当前温度 (s8)

  • 最低温度 (s8)

  • 最高温度 (s8)

  • 未知字段 (u8)

注意

TODO:找出最后一个字节的含义。

2.4. ACPI 电池匹配算法

用于将 ACPI 电池与索引匹配的算法基于 OEM 软件日志消息中找到的信息。

基本上,对于每个新的 ACPI 电池,索引 1 到 3 后的电池序列号都与 ACPI 电池的序列号进行比较。由于 ACPI 电池的序列号可以编码为普通整数或十六进制值,因此需要检查两种情况。然后选择第一个具有匹配序列号的索引。

序列号 0 表示相应的索引未与实际电池关联,或者关联的电池不存在。

某些机器(如戴尔 Inspiron 3505)仅支持单个电池,因此忽略电池索引。因此,驱动程序依赖于 ACPI 电池挂钩机制来发现电池。

2.5. 逆向工程 DDV WMI 接口

  1. 找到一个受支持的戴尔笔记本电脑,通常是 2020 年之后生产的。

  2. 转储 ACPI 表并搜索 WMI 设备(通常称为“ADDV”)。

  3. 解码相应的 bmof 数据并查看 ASL 代码。

  4. 尝试通过将控制流与其他 ACPI 方法(例如电池相关方法的 _BIX 或 _BIF)进行比较来推断特定 WMI 方法的含义。

  5. 使用内置的 UEFI 诊断程序查看风扇/散热相关方法的传感器类型/值(有时覆盖静态 ACPI 数据字段可用于测试不同的传感器类型值,因为在某些机器上,此数据不会在热重置时重新初始化)。

或者

  1. 加载 dell-wmi-ddv 驱动程序,如有必要,使用 force 模块参数。

  2. 使用 debugfs 接口访问原始风扇/热传感器缓冲区数据。

  3. 将数据与内置的 UEFI 诊断程序进行比较。

如果您的戴尔笔记本电脑上提供的 DDV WMI 接口版本不受支持,或者您看到未知的风扇/热传感器,请在 bugzilla 上提交错误报告,以便可以将它们添加到 dell-wmi-ddv 驱动程序中。

有关更多信息,请参见报告问题