2024年acpi-compliant embedded

acpi-compliant embedded关于 WMI ACPI 建议先通读 1 lt 原创 BIOS 知识点滴 Follow Bini 系列之 WMI ACPI gt 了解 WMI ACPI 能提供什么后 再参考 MSDN 2 windows instrumentat wmi and acpi 另外 3 windows

    关于WMI ACPI,建议先通读:1.<[原创]BIOS知识点滴Follow Bini系列之---WMI ACPI>了解WMI ACPI能提供什么后,再参考MSDN 2.<Windows Instrumentation: WMI and ACPI>,另外,3.<文件系统驱动编程基础篇之4——Wmi管理规范 mof文件>也可以作为1.的补充。需要特别说明的,1.中留了demo程序,一般读者没有环境测试(也不建议用真实机器测试,万一不开机损失挺大的),这时倒可以参考我的文章<解密OEM Bios导出给Windows的接口----导出OEM内部使用的WMI接口>,从现有BIOS中提取MOF和WMI接口用于学习。

    本文应该是<解密OEM Bios导出给Windows的接口----导出OEM内部使用的WMI接口>一文的原理补充和总结,主要补充一下MOF资源文件和ACPI中各个WMI接口的关系。要厘清他们的关系,得先用<[原创]BIOS知识点滴Follow Bini系列之---WMI ACPI>文中使用的代码片:

Mof描述文件:

// Author: bini.Yi易祝兵 http://www.ufoit.com 2008-09-24 // File: demowmi.mof //{-C6A3-40fa-BADB-8A} //IMPLEMENT_OLECREATE //0x, 0xc6a3, 0x40fa, 0xba, 0xdb, 0x8a, 0x26, 0x52, 0x83, 0x41, 0x00); [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Events"), guid("{-C6A3-40fa-BADB-8A}") ] class DemoWMIData { [key, read] string InstanceName; [read] boolean Active; [WmiDataId(1), read, write, Description("description") ] uint32 Data; };

ASL(WMI)文件:

// Author: bini.Yi易祝兵 2008-09-24 // File: demowmi.ASL Device(DWMI) { // PNP0C14 is PNP ID assigned to WMI mapper Name(_HID, EISAID("PNP0C14")) Name(_UID, 0x0) Name(_WDG, Buffer(){ // {-C6A3-40fa-BADB-8A} 0x00, 0x24, 0x14, 0x39, 0xA3, 0xC6, 0xFA, 0x40, 0xBA, 0xDB, 0x8A, 0x26, 0x52, 0x83, 0x41, 0x00, //GUID 0x30, 0x30, //'00' Object ID 0x01, //Instance Num 0x01, // 00 = Demo }) Name(DD00, 0) Method(WQ00, 1) { DBGS("Demo Wmi Get Function:") DW2H(DD00) Return(DD00) } Method(WS00, 2) { DBGS("Demo Wmi Set Function:") DW2H(ARG1) Store(ARG1, DD00) } }

WMI ACPI调用模型:

MOF描述文件是最终导出给用户调用的接口,WMI ACPI中的ASL code部分则是MOF所描述的Class的实现。如果读者反复阅读和对比MOF/ASL文件,会觉得他们很像动态链接库:MOF文件如同Class头文件,负责约定和导出调用接口。ASL如同DLL文件负责DLL函数的实现。至于_WDG对象,则充当了DLL的EAT(函数地址表)的中间人角色(其实还依赖于_WDG对象中嵌入式MOF文件,后面会提到),当然EAT表中可能会有若干表项组成,_WDG对象也是如此。_WDG中单个表项具有如下数据结构:

typedef struct _Mapper { GUID guid; // GUID that names data block union { CHAR ObjectId[2]; // 2-character ACPI ID (Data Blocks and Methods) struct { UCHAR NotificationValue; // Byte value passed by event handler control method UCHAR Reserved[1]; } NotifyId; } USHORT InstanceCount; // Number of separate instances of data block USHORT Flags; // Flags }Mapper;

当所有表项结合在一起,形成一个巨大的Mapper[N]数组,这个数组最终构成_WDG对象。而Mof资源文件的编写者(无疑是OEM厂商了),会为_WDG对象中每个Mapper[i]制作Class。每个Class都有Mapper[i].guid对应。

调用过程(纯个人臆测,不过我觉得挺有道理的!):

  1. WMI的调用者(想象成DLL的调用者),根据MOF资源文件(想象成Class头文件)描述的WMI接口,以ClassName的形式去调用ASL code的(想象成DLL文件)时,ACPI.sys会到ACPI命名空间中先查找_WDG对象。
  2. 找到_WDG对象后,遍历其Mapper[N]数组,并定位到Mapper[i].Flags==0的数组项,该数组项指向一块buffer,buffer中包含编译后的MOF文件,MSDN称之为嵌入式MOF资源。
  3. ACPI.sys从嵌入式Mof资源中查找匹配的ClassName(因为上层使用Class调用WMI)。大家不妨回忆前面Mof资源描述文件中含有guid和ClassName
  4. 获得ClassName后,进一步可以获得接口名的guid,然后回到_WDG的Mapper[N]数组中,根据Mapper[i].guid查找与调用者GUID相匹配的数组项。
  5. 匹配到GUID后,ACPI.sys会取出Mapper[i].ObjectID成员,成员中包含xx值,ACPI.sys将WQ/WS和xx拼接,形成WQxx或者WSxx,然后去ACPI命名空间调用相应的WQxx或者WSxx方法。

    <[原创]BIOS知识点滴Follow Bini系列之---WMI ACPI>作为一个demo程序,_WDG对象的Mapper[N]数组只有一个数组项,没有体现上述搜索过程。所以,我换一个N>1的例子,嗯,那就用从<解密OEM Bios导出给Windows的接口----导出OEM内部使用的WMI接口>提取出来的ThinkPad的_WDG对象:

 Device (WMI2) { Name (_HID, EisaId ("PNP0C14") /* Windows Management Instrumentation Device */) // _HID: Hardware ID Name (_UID, 0x02) // _UID: Unique ID Name (_WDG, Buffer (0x64) { //==============Mapper[0]============== 0xF1, 0x24, 0xB4, 0xFC, 0x5A, 0x07, 0x0E, 0x4E, 0xBF, 0xC4, 0x62, 0xF3, 0xE7, 0x17, 0x71, 0xFA, 0x41, 0x37, 0x01, 0x01, //==============Mapper[1]============== 0xE3, 0x5E, 0xBE, 0xE2, 0xDA, 0x42, 0xDB, 0x49, 0x83, 0x78, 0x1F, 0x52, 0x47, 0x38, 0x82, 0x02, 0x41, 0x38, 0x01, 0x02, //==============Mapper[2]============== 0x9A, 0x01, 0x30, 0x74, 0xE9, 0xDC, 0x48, 0x45, 0xBA, 0xB0, 0x9F, 0xDE, 0x09, 0x35, 0xCA, 0xFF, 0x41, 0x39, 0x0A, 0x05, //==============Mapper[3]============== 0x03, 0x70, 0xF4, 0x7F, 0x6C, 0x3B, 0x5E, 0x4E, 0xA2, 0x27, 0xE9, 0x79, 0x82, 0x4A, 0x85, 0xD1, 0x41, 0x41, 0x01, 0x06, //==============Mapper[4]============== 0x21, 0x12, 0x90, 0x05, 0x66, 0xD5, 0xD1, 0x11, 0xB2, 0xF0, 0x00, 0xA0, 0xC9, 0x06, 0x29, 0x10, 0x42, 0x42, 0x01, 0x00 })

这是ThinkPad T460部分_WDG对象,其中有5个数组项,最后是一个特殊数组项,暂不讨论。其他几个数组项对应的WMI接口如下:

On Error Resume Next Set fso = CreateObject("Scripting.FileSystemObject") Set a = fso.CreateTextFile("lenvon.log", True) Set Service = GetObject("winmgmts:{impersonationLevel=impersonate}!root/wmi") Rem Lenovo_PreloadLanguage - Preload Language Set enumSet = Service.InstancesOf ("Lenovo_PreloadLanguage") a.WriteLine("Lenovo_PreloadLanguage") for each instance in enumSet a.WriteLine(" InstanceName=" & instance.InstanceName) a.WriteLine(" instance.CurrentSetting=" & instance.CurrentSetting) next 'instance Rem Lenovo_SetPreloadLanguage - Set Preload Language Set enumSet = Service.InstancesOf ("Lenovo_SetPreloadLanguage") a.WriteLine("Lenovo_SetPreloadLanguage") for each instance in enumSet a.WriteLine(" InstanceName=" & instance.InstanceName) next 'instance Rem Lenovo_PlatformSetting - Platform Setting Set enumSet = Service.InstancesOf ("Lenovo_PlatformSetting") a.WriteLine("Lenovo_PlatformSetting") for each instance in enumSet a.WriteLine(" InstanceName=" & instance.InstanceName) a.WriteLine(" instance.CurrentSetting=" & instance.CurrentSetting) next 'instance Rem Lenovo_SetPlatformSetting - Set Platform Setting Set enumSet = Service.InstancesOf ("Lenovo_SetPlatformSetting") a.WriteLine("Lenovo_SetPlatformSetting") for each instance in enumSet a.WriteLine(" InstanceName=" & instance.InstanceName) next 'instance a.Close Wscript.Echo "lenvon Test Completed, see lenvon.log for details"

由于ThinkPad用的是嵌入式MOF,我无法获得原始的Mof文件(如同demowmi.mof),但是运行这段vbs脚本,可以推测出mof可能的内容:

//entry 1 [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Platform Setting"), guid("{0x9A, 0x01, 0x30, 0x74, 0xE9, 0xDC, 0x48, 0x45, 0xBA, 0xB0, 0x9F, 0xDE, 0x09, 0x35, 0xCA, 0xFF}") ] class Lenovo_PlatformSetting { [key, read] string InstanceName; [read] boolean Active; [WmiDataId(0x0A), read, write, Description("platfrom setting") ] String CurrentSetting; }; //entry 2 [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Platform Setting"), guid("{0x03, 0x70, 0xF4, 0x7F, 0x6C, 0x3B, 0x5E, 0x4E, 0xA2, 0x27, 0xE9, 0x79, 0x82, 0x4A, 0x85, 0xD1}") ] class Lenovo_SetPlatformSetting { [key, read] string InstanceName; [read] boolean Active; [WmiMethodId, Description("Set platfrom setting") ] } //entry 3 [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Platform Setting"), guid("{0xF1, 0x24, 0xB4, 0xFC, 0x5A, 0x07, 0x0E, 0x4E, 0xBF, 0xC4, 0x62, 0xF3, 0xE7, 0x17, 0x71, 0xFA}") ] class Lenovo_PreloadLanguage { [key, read] string InstanceName; [read] boolean Active; [WmiDataId(0x01), read, write, Description("Preload Langugage") ] String CurrentSetting; } //entry 4 [WMI, Dynamic, Provider("WmiProv"), Locale("MS\\0x409"), Description("Platform Setting"), guid("{}") ] class Lenovo_SetPreloadLanguage { [key, read] string InstanceName; [read] boolean Active; [WmiMethodId, Description("Set Set Preload Language") ] }

VBS中的InstanceOf:

    借由Wmi Code generate生成的ACPI WMI接口测试脚本中有不少InstanceOf\instance语句:

Set enumSet = Service.InstancesOf ("Lenovo_PlatformSetting") a.WriteLine("Lenovo_PlatformSetting") for each instance in enumSet a.WriteLine(" InstanceName=" & instance.InstanceName) a.WriteLine(" instance.CurrentSetting=" & instance.CurrentSetting) next 'instance

(以下为个人理解:)前面说过Mof资源文件为_WDG对象的每个数组项编辑一个Class。套用面向对象思维,必须创建类对象(实例化Class)才能访问其成员,所以vbs脚本中

Set enumSet = Service.InstancesOf ("Lenovo_PlatformSetting")

应该就是创建Mof资源文件中描述的Lenovo_PlatformSetting类对象,并命名为enumSet。

VBS中"for each instance in enumSet":

如果把enumSet理解为某个Class的对象,那么vbs脚本中的instance就是Mof资源文件中描述的Class的各个成员变量。需要注意的是:阅读vbs测试脚本时,我们发现它是以循环遍历的方式访问类对象的各个成员的,所以,我们应该认为类对象中成员变量的类型一致,因此可以构成一个数组。另外,特殊情况下,如果数组中只有一项,那么数组将简化为普通变量。为了与_WDG对象中的Mapper[N]数组区分,我们用Item数组表示Class中类型一致的成员变量。Item数组的长度(即instance的数量或者说for循环的次数)在_WDG对象的Mapper[i].instanceCount中有指定:

//再次搬出这个结构! typedef struct _Mapper { GUID guid; // GUID that names data block union { CHAR ObjectId[2]; // 2-character ACPI ID (Data Blocks and Methods) struct { UCHAR NotificationValue; // Byte value passed by event handler control method UCHAR Reserved[1]; } NotifyId; } USHORT InstanceCount; // <----就是它指定instance的数量 USHORT Flags; // Flags }Mapper;

以ThinkPad T460P为例:

Class:Lenovo_PlatformSetting.InstanceCount=0x0A;

Class:Lenovo_SetPlarformSeting.InstanceCount=0x01;

这些都可以在_WDG对象Mapper[i].InstanceCount中找到线索:

 Device (WMI2) { Name (_HID, EisaId ("PNP0C14") /* Windows Management Instrumentation Device */) // _HID: Hardware ID Name (_UID, 0x02) // _UID: Unique ID Name (_WDG, Buffer (0x64) { ... //==============Mapper[2]============== 0x9A, 0x01, 0x30, 0x74, 0xE9, 0xDC, 0x48, 0x45, 0xBA, 0xB0, 0x9F, 0xDE, 0x09, 0x35, 0xCA, 0xFF, 0x41, 0x39, 0x0A, <----- Lenovo_PlatformSetting.InstanceCount 0x05, //==============Mapper[3]============== 0x03, 0x70, 0xF4, 0x7F, 0x6C, 0x3B, 0x5E, 0x4E, 0xA2, 0x27, 0xE9, 0x79, 0x82, 0x4A, 0x85, 0xD1, 0x41, 0x41, 0x01, 0x06, <----- Lenovo_PlarformSeting.InstanceCount=0x01

虽然,Item数组的长度随着Mapper[i].InstanceCount的值可以固定下来,但是Item数组的数组项类型却没有指定。简单的可能是UINT类型的Item数组,复杂的可能是Buffer或者Package类型的Item(再次注意,我这里用的是Item数组)。仍以ThinkPad T460P Lenovo_PlarformSeting类为例,Lenovo_PlarformSeting类函数为Lenovo_PlarformSeting,在WMI ACPI中的实现为:Method (WQA9, 1, NotSerialized)

Method (WQA9, 1, NotSerialized) { Acquire (\_SB.WMI1.MWMI, 0xFFFF) If ((\WMIS (0x09, Arg0) != 0x00)) { Release (\_SB.WMI1.MWMI) Return ("") } Local0 = DerefOf (ITEM [\WITM]) Local1 = DerefOf (Local0 [0x00]) Local2 = DerefOf (Local0 [0x01]) Concatenate (Local2, ",", Local6) Local3 = DerefOf (VSEL [Local1]) Concatenate (Local6, DerefOf (Local3 [\WSEL]), Local7) Release (\_SB.WMI1.MWMI) Return (Local7) }

Method WQA9根据唯一的参数Arg0,从ACPI命名空间ITEM对象中获取数值。至于ITEM对象,它长这样

Name (ITEM, Package (0x06) { Package (0x02) { 0x00, "InhibitEnteringThinkPadSetup" }, Package (0x02) { 0x00, "MTMSerialConcatenation" }, Package (0x02) { 0x00, "SwapProductName" }, Package (0x02) { 0x00, "ComputraceMsgDisable" }, Package (0x02) { 0x00, "CpuDebugEnable" }, Package (0x02) { 0x00, "PasswordAfterBootDeviceList" } })

额,这个结构有点复杂,看不懂!我们慢慢梳理以下:

1.根据ACPI语法,Package可以认为结构体,也可以认为是数组。把最内层的Package (0x2)当作一个只有2个成员变量的结构体:

typedef struct _SettingEntry { UINT32 SettingVal; char* SettingName; }SettingEntry;

外层Package理所当然可以认为是这样的结构体类型的Item数组,数组项共6项(虽然_WDG指定该数组项应该共有10项,但实际只有6项,而且访问超出部分也没发生异常!):

SettingEntry T460P[] = { 
  {0x00,"InhibitEnteringThinkPadSetup"}, {0x00,"MTMSerialConcatenation"}, {0x00,"SwapProductName"}, {0x00,"ComputraceMsgDisable"}, {0x00,"CpuDebugEnable"}, {0x00,"PasswordAfterBootDeviceList"} };

回到我们的标题:VBS中"for each instance in enumSet",vbs每轮循环,其实就是访问T460P[n]。对于有些OEM厂商会进行复杂的访问,比如仅仅访问T460P[n].SettingVal或者T460P[n].SettingName。这时就会在ASL code中看到大量的DerefOf语句。

_WDG中特殊的Flag:

每个_WDG对象Mapper[N]数组中都有一个(唯一的一个)特殊的数组项,它具有特殊的Flags值,其值为0x00。这个特殊的Mapper[i]数组项只有一个作用:提供buffer,存储编译后的Mof资源文件,因此这个表项本身并不会出现在Mof资源描述文件中。但它是构成调用ACPI WMI接口过程中重要的一步,没有它,无法实现Class名到ACPI Method的转换。

知秋君
上一篇 2024-07-03 21:02
下一篇 2024-11-07 19:12

相关推荐