ACPI WMI 杂记

    关于WMI ACPI,建议先通读:1.<[原创]BIOS知识点滴Follow Bini系列之---WMI ACPI>了解WMI ACPI能提供什么后,再参考MSDN 2.,另外,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
//{39142400-C6A3-40fa-BADB-8A2652834100}
//IMPLEMENT_OLECREATE
//0x39142400, 0xc6a3, 0x40fa, 0xba, 0xdb, 0x8a, 0x26, 0x52, 0x83, 0x41, 0x00);

[WMI,
Dynamic,
Provider("WmiProv"),
Locale("MS\\0x409"),
Description("Events"),
guid("{39142400-C6A3-40fa-BADB-8A2652834100}")
]
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(){
        // {39142400-C6A3-40fa-BADB-8A2652834100}
        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的转换。

你可能感兴趣的:(EFI)