peterhu 发表于 2009-6-3 14:24:19

S3/S4/S5 Long Run Test

S3/S4/S5 Long Run Test

1. Why need it?

NB研发的工程中,需要跑很多的测试项目,其中long run S3/S4/S5就是非常重要的测试项目。而且对于测试结果非常看重,一般long run测试fail就没法出货。常规的测试方法就是10~20台机器测试S3/S4/S5 1000 cycle,如果fail率在万分之几就有可能要挂了。于是我就有了写一个long run S3/S4/S5测试程序的想法了。

2. How to implement?

心动不如行动,Let’s go!经过几番查阅MSDN,S3/S4的功能已经有些眉目了,可是如何实现S5 wakeup呢?猛翻SDK&DDK,狂试API结果发现不行,好像没有相关的API能做到这件事。最后联系微软的FAE,他们的结论也是如此。既然常规做法不行,那么我就另辟蹊径。我知道BIOS Setup menu 有个选项可以设置RTC唤醒,只要机器还有电S5的状态下也可以唤醒机器。那么BIOS是怎么做的呢?我请教了BIOS 得知需要做两个动作:1.设置CMOS中的alarm time 2:将chipset RTC_EN bit置起然后进入S5。系统在alarm time到达时会产生wakeup event,Chipset会送power sequence系统就会开机。通过一个IO port driver,完成上述过程后我调用API ExitWindowsEx(EWX_SHUTDOWN|EWX_FORCE,0);关机,我等的头发都白了系统也没有唤醒L,到底什么地方出了问题呀?后来我用SE.exe模拟上述过程,结果发现RTC_EN被我修改过之后,过了一会居然又被改回原来的值了,看起来windows在幕后做了不少小动作哦J,这样一来这个方法就行不通了,那么该怎么办呢?答案就是使用EC_BIOS去做,只要有电EC就会运行,那么S5时EC仍然在工作,所以只需要EC在特定的条件下模拟一个开机动作即可。思路已经有了那么该如何实现呢?方法有两种:a.通过IO port driver 下81 cmd给66 port,然后将EC ram index和value送给62 port,这样就可以修改EC ram了,然后EC在S5时检查该EC ram值如果非0就倒数计时,时间到了就模拟一个开机动作,从而完成S5 wake up。b.同样是写EC ram不过使用不同的方法,我们可以在BIOS asl code里定制一个WMI ACPI device,并且在该device scope提供query/set EC ram的方法,并且提供一个WMI ACPI的driver,这样应用程序就可以方便的操纵EC ram了,而且一旦完成这只driver就可以完成非常多的增值部分,比如我们可以写一个程序读写EC ram中的battery info;或者我们可以写一个读取thermal info程序等等。
如下图1是程序的运行画面,该测试程序实现了S3/S4/S5的功能S3/S4在xp下面可以正常工作,而

vista下无法唤醒L。下S3/S4是通下述代码实现的:





图 1




//hibernate and standby

int
CAutoPowerOnShutdownDlg::SetPower(BOOL
bSuspend,BOOL
bForce)

{


TOKEN_PRIVILEGES
tp;


HANDLE
hToken;


LUID
luid;


LPTSTR
MachineName=NULL;


                        if(!OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES,&hToken
))


{

                                    return
RTN_ERROR;


}

                        if(!LookupPrivilegeValue(MachineName,
SE_SHUTDOWN_NAME,
&luid))


{

                                    return
RTN_ERROR;


}


tp.PrivilegeCount
=
1;


tp.Privileges.Luid
=
luid;


tp.Privileges.Attributes
=
SE_PRIVILEGE_ENABLED;


AdjustTokenPrivileges(hToken,
FALSE,
&tp,
sizeof(TOKEN_PRIVILEGES),NULL,

NULL
);


SetSystemPowerState(bSuspend,bForce);


                        return
0;


}


S3/S4 wake up function是通过在下去的时候设置一个WaitableTimer实
现唤醒的功能代码如下所示:

case
PBT_APMSUSPEND:


{

HANDLE hTimer=::CreateWaitableTimer(NULL,TRUE,CString(_T("WaitForResume")));

                if(!hTimer)


{



MessageBox(CString(_T("Fail
to
create
waitable
timer!")));


}





hTimer=OpenWaitableTimer(TIMER_ALL_ACCESS,TRUE,CString(_T("WaitForResume")));


LARGE_INTEGER
liDueTime;


liDueTime.QuadPart=m_DlInf.m_TimeSnd *1000*1000*(-10);

                if(!::SetWaitableTimer(hTimer,&liDueTime,0,NULL,NULL,TRUE))


{



MessageBox(CString(_T("Fail
to
set
waitable
timer!")));

                        break;


}


}
break;

最关键的部分就是S5 wake up了我们来看看它的实现代码吧,代码中最核
心的部分就是同连接到我们定制的WMI class MSI_System,然后通过
HRESULT PutInstance(

IWbemClassObject* pInst,

LONG lFlags,

IWbemContext* pCtx,

IWbemCallResult** ppCallResult);修改该class的System变量而该变量在BIOS的asl中被定义在EC ram之
中的特定位置,这样就会改变EC ram中的值了。


void CAutoPowerOnShutdownDlg::SetS4WakeTimer(unsigned char seconds)

{

HRESULT hres;




// Step 1: --------------------------------------------------



// Initialize COM. ------------------------------------------




hres =
CoInitializeEx(0, COINIT_MULTITHREADED);



if (FAILED(hres))



{



cout << "Failed to initialize COM library. Error code = 0x"



<< hex << hres << endl;
                                    return;



}




// Step 2: --------------------------------------------------


// Set general COM security levels --------------------------



// Note: If you are using Windows 2000, you need to specify -



// the default authentication credentials for a user by using



// a SOLE_AUTHENTICATION_LIST structure in the pAuthList ----



// parameter of CoInitializeSecurity ------------------------




hres =
CoInitializeSecurity(




NULL,




-1,

// COM authentication




NULL,
// Authentication services





NULL,
// Reserved


RPC_C_AUTHN_LEVEL_DEFAULT,
// Default authentication


RPC_C_IMP_LEVEL_IMPERSONATE, // Default Impersonation



NULL,
// Authentication info


EOAC_NONE,
// Additional capabilities




NULL
// Reserved




);





if (FAILED(hres))


{


cout << "Failed to initialize security. Error code = 0x"


<< hex << hres << endl;


CoUninitialize();
                        return;


}




// Step 3: ---------------------------------------------------


// Obtain the initial locator to WMI -------------------------



IWbemLocator *pLoc = NULL;



hres = CoCreateInstance(


CLSID_WbemLocator,



0,


CLSCTX_INPROC_SERVER,


IID_IWbemLocator, (LPVOID *) &pLoc);




if (FAILED(hres))


{


cout << "Failed to create IWbemLocator object."


<< " Err code = 0x"


<< hex << hres << endl;


CoUninitialize();


return ;
// Program has failed.


}



// Step 4: -----------------------------------------------------



// Connect to WMI through the IWbemLocator::ConnectServer method



IWbemServices *pSvc = NULL;




// Connect to the root\cimv2 namespace with


// the current user and obtain pointer pSvc


// to make IWbemServices calls.


hres = pLoc->ConnectServer(


_bstr_t(L"ROOT\\WMI"), // Object path of WMI namespace


NULL,
// User name. NULL = current user


NULL,
// User password. NULL = current


0,
// Locale. NULL indicates current


NULL,
// Security flags.


0,
// Authority (e.g. Kerberos)


0,
// Context object


&pSvc
// pointer to IWbemServices proxy


);




if (FAILED(hres))


{


cout << "Could not connect. Error code = 0x"


<< hex << hres << endl;


pLoc->Release();



CoUninitialize();




return ;
// Program has failed.


}



cout << "Connected to ROOT\\CIMV2 WMI namespace" << endl;




// Step 5: --------------------------------------------------


// Set security levels on the proxy -------------------------



hres = CoSetProxyBlanket(


pSvc,
// Indicates the proxy to set


RPC_C_AUTHN_WINNT,
// RPC_C_AUTHN_xxx


RPC_C_AUTHZ_NONE,
// RPC_C_AUTHZ_xxx


NULL,

// Server principal name


RPC_C_AUTHN_LEVEL_CALL,
// RPC_C_AUTHN_LEVEL_xxx


RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx


NULL,
// client identity


EOAC_NONE
// proxy capabilities


);



if (FAILED(hres))


{


cout << "Could not set proxy blanket. Error code = 0x"


<< hex << hres << endl;


pSvc->Release();


pLoc->Release();



CoUninitialize();


return ;
// Program has failed.


}



// Step 6: --------------------------------------------------


// Use the IWbemServices pointer to make requests of WMI ----



// For example, get the name of the operating system


IEnumWbemClassObject* pEnumerator = NULL;



hres = pSvc->ExecQuery(


bstr_t("WQL"),


bstr_t("SELECT * FROM MSI_System"),


WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,


NULL,


&pEnumerator);




if (FAILED(hres))


{


cout << "Query for operating system name failed."


<< " Error code = 0x"


<< hex << hres << endl;


pSvc->Release();


pLoc->Release();


CoUninitialize();


return;

// Program has failed.


}



// Step 7: -------------------------------------------------


// Get the data from the query in step 6 -------------------




IWbemClassObject *pclsObj;


ULONG uReturn = 0;


int count = 0;


while (pEnumerator)


{



HRESULT hr = pEnumerator->Next(WBEM_INFINITE, 1,



&pclsObj, &uReturn);




if(0 == uReturn)



{



break;



}

                                    if(++count == 3)

{

CString tmp;

tmp.Format(_T("%d"),seconds);


// Set up the property value.

VARIANT v;

VariantInit(&v);

V_VT(&v) = VT_BSTR;

V_BSTR(&v) = tmp.AllocSysString(); // - decimal format, not hex


hr = pclsObj->;Put(L"System",0,&v,CIM_UINT8);
                                                // Clean up.

VariantClear(&v);


                                                if (hr == WBEM_E_ACCESS_DENIED)

{

printf("WBEM_E_ACCESS_DENIED\n");

                                                            // Processing to handle specific error code

}
                                                else if (hr == WBEM_S_DUPLICATE_OBJECTS)

{

printf("WBEM_S_DUPLICATE_OBJECTS\n");
                                                            // All other cases, including errors specific to COM

}
                                                else if (hr == WBEM_E_INVALID_OBJECT)

{

printf("WBEM_E_INVALID_OBJECT\n");

}
                                                else if(hr == WBEM_E_INVALID_PARAMETER)

{

printf("WBEM_E_INVALID_PARAMETER\n");

}
                                                else if(hr == WBEM_S_NO_ERROR)

{

printf("WBEM_S_NO_ERROR\n");

}
                                                else

printf("ERROR:%x",hr);


HRESULT hRes = pSvc->;PutInstance(pclsObj,WBEM_FLAG_CREATE_OR_UPDATE,0,0);


                                                // Check for specific error and status codes.
                                                if (hRes == WBEM_E_ACCESS_DENIED)

{

printf("WBEM_E_ACCESS_DENIED\n");

                                                            // Processing to handle specific error code

}
                                                else if (hRes == WBEM_S_DUPLICATE_OBJECTS)

{

printf("WBEM_S_DUPLICATE_OBJECTS\n");
                                                            // All other cases, including errors specific to COM

}
                                                else if (hRes == WBEM_E_INVALID_OBJECT)

{

printf("WBEM_E_INVALID_OBJECT\n");

}
                                                else if(hRes == WBEM_E_INVALID_PARAMETER)

{

printf("WBEM_E_INVALID_PARAMETER\n");

}
                                                else if(hRes == WBEM_S_NO_ERROR)

{

printf("WBEM_S_NO_ERROR\n");

}

}



}



// Cleanup


// ========




pSvc->Release();


pLoc->Release();


pEnumerator->Release();


pclsObj->Release();


CoUninitialize();


}
以上就是该程序实现的全部过程,完整的source code可以在附件下载。S5 wake up使用了WMI ACPI,
该部分比较复杂,我在后续会发一个WMIACPI的系列,完整的描述WMI ACPI实现过程中

BIOS,EC,OS,Driver分别扮演的角色。

that's all!

Peter

[ 本帖最后由 peterhu 于 2009-6-3 14:25 编辑 ]

woodern 发表于 2009-6-17 08:29:28

good.MARKED.

Faintsnow 发表于 2009-7-11 12:39:02

Peter老大,我试了下S3,你通过重载WindowProc将机器从S3唤醒.但是唤醒以后Monitor没有显示啊?

蓝色永恒 发表于 2011-9-15 17:14:03

请教一个问题,S3/4/5大家都做出来了, S1 的功能不知道有没有相应的 API可以调用呢?Suspend.exe 有类似的测试S1的选项,不知道是怎么实现的,谁知道麻烦讲一下,谢谢
页: [1]
查看完整版本: S3/S4/S5 Long Run Test