S3/S4/S5 Long Run Test
S3/S4/S5 Long Run Test1. 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 编辑 ] good.MARKED. Peter老大,我试了下S3,你通过重载WindowProc将机器从S3唤醒.但是唤醒以后Monitor没有显示啊? 请教一个问题,S3/4/5大家都做出来了, S1 的功能不知道有没有相应的 API可以调用呢?Suspend.exe 有类似的测试S1的选项,不知道是怎么实现的,谁知道麻烦讲一下,谢谢
页:
[1]