目錄
引入.
Part 1. Bios側對開啟ACPI的支援.
Part 2. Windows側開啟ACPI.
引入.
由于windows裝置驅動程式被設計成以裝置堆棧(Device stack)的形式,下層驅動向上提供支援,是以我們習慣于不加思考的在裝置棧的上層使用來自下層驅動的各種已有功能。當我還在做驅動程式時,經常見到這些句話:
A1."功能驅動(Fdo)收到IRP_MJ_PNP&IRP_MN_START後,會向總線驅動(Pdo)查詢并正确配置所需的硬體資源,如IO Port/IRQ/Mem等,然後才開始工作"。這段話我一直感到困惑:功能驅動的硬體資源來自總線驅動,那麼總線驅動的資源由誰提供?可能有人會這樣釋疑:由實體總線仲裁配置設定。
A2."裝置驅動從D0進入D3,儲存裝置上下文以後,裝置(特指硬體上的裝置)将進入D3 Sleep狀态"。這段話就更困惑了,為什麼儲存裝置上下文(往往是驅動層面上的上下文)後,硬體就能下電了?
A3.OS進入Sleep後,當支援喚醒功能的裝置收到中斷信号後,喚醒OS。其實,進入Sleep狀态後,CPU不會運作任何指令,OS和驅動程式不可能執行;另外,OS作為驅動程式的載體,不可能驅動程式先于OS提前醒來并喚醒OS的情況(驅動程式在夢遊?)。那麼,所謂的驅動程式Wakeup功能又是指什麼?
它是windows對ACPI spec中反複出現的OSPM的平台實作)。windows通過ACPI.sys,借助BIOS的實作上述功能A1/A2;GPIO信号跳變後,觸發SCI中斷,引起Bios執行平台喚醒功能,并在此過程中重新加載OS,最終實作上述功能A3。ACPI如此重要,值得我們好好研究。本文從兩個側面介紹win7 x86上開啟ACPI的過程:Bios側和windows側。
Part 1. Bios側對開啟ACPI的支援.
根據ACPI spec(Version 6.2),FADT表中的SMI_CMD和ACPI_ENABLE與ACPI開啟有關:

如spec所述,Bios在初始化過程中配置完包含SMI_CMD和ACPI_ENABLE(重點強調,這是Bios設定的,相當于提供給OS的接口。對于同一台機器不論裝什麼OS,這個值是固定的)的整個ACPI table,然後加載到記憶體。OS在引導過程中會向SMI_CMD指定的端口寫入ACPI_ENABLE設定的值,觸發SMI中斷;SMI中斷觸發後會進入Bios的SMM Handler,由Bios執行開啟ACPI所需的設定。到vm上看下這兩項的設定:
SMI_CMD是0xB2,這是一個特殊值,Intel PCH spec上對這個端口有詳細說明:當APMC_En bit enable後,往APM_CNT,就是RW中的0xB2寫入數值就會引起SMI中斷。下面截圖選自Intel PCH spec:
UEFI架構中會用下列形式注冊SMI Handler,當往0xB2端口寫入SwSmiInputValue時,就會觸發SMI中斷,并執行SMI處理函數EnableAcpiCallback(附注,SMI處理函數的運作對于OS而言是透明的,這句話讀者自己細品)
SwContext.SwSmiInputValue = (UINTN) PcdGet8 (PcdAcpiEnableSwSmi);
Status = SwDispatch->Register (
SwDispatch,
EnableAcpiCallback,
&SwContext,
&SwHandle
);
同理,Bios為了支援windows開啟ACPI,也會以同樣的方式注冊SMI處理函數。對于VMware,它注冊的值是0xF0,注冊後,往ACPI table Fadt->ACPI_Enable域寫入0xF0,之後默默的等windows開啟ACPI。
Part 2. Windows側開啟ACPI.
debug前的準備工作:
bcdedit /debug on
bcdedit /bootdebug on ;不開這個,windows開啟ACPI的瞬間,windbg和目标機的調試會話會被重置
根據洩露的windows xp源碼,可以發現Xp時代,開啟ACPI的相關代碼位于ACPIEnableEnterACPIMode 函數中:
//busdrv\acpi\driver\shared\acpienbl.c
VOID
ACPIEnableEnterACPIMode (
VOID
);
/*++
Routine Description:
This routine is called to enter ACPI mode
Arguments:
None
Return Value:
None
--*/
不過這段代碼來自Xp,我們調試的目标是win7,調試前得先确認下這部分代碼是否存在變化:
kd>x ACPI!ACPIEnableEnterACPIMode ;搜尋函數名
ACPI!ACPIEnableEnterACPIMode ;居然函數還在,看來Win7還是用這個函數開啟ACPI
kd> bp acpi!ACPIEnableEnterACPIMode
kd> g
Breakpoint 0 hit
ACPI!ACPIEnableEnterACPIMode:
88cdbce0 8bff mov edi,edi
當觸發斷點後,看下調用堆棧等資訊,可以看出此時OS處于初始化Phase1階段。
kd> g
Breakpoint 0 hit
ACPI!ACPIEnableEnterACPIMode:
88cc2ce0 8bff mov edi,edi
kd> kb
# ChildEBP RetAddr Args to Child
00 8078b644 88cc2e25 00000000 8078b66c 88cfde24 ACPI!ACPIEnableEnterACPIMode
01 8078b650 88cfde24 00000000 82a0f940 88cf5ae0 ACPI!ACPIEnableInitializeACPI+0x1f
02 8078b66c 88cbe556 87d4cc60 87e548a0 00000000 ACPI!ACPIInitialize+0xe2
03 8078b69c 88d050c2 87d4cc60 886e81a0 88d04f38 ACPI!ACPIInitStartACPI+0x6a
04 8078b6c8 88cb927e 87d4cc60 886e8100 87d4cc60 ACPI!ACPIRootIrpStartDevice+0x18a
05 8078b6f8 82a7c11a 87d4cc60 87e548a0 886e8258 ACPI!ACPIDispatchIrp+0x13a
06 8078b718 82f8f531 00000000 87e535d8 87d4b008 nt!IofCallDriver+0x7e
07 8078b734 82a89506 8078b76c 82a892ea 87d4e4a0 nt!PnpAsynchronousCall+0x109
08 8078b7a0 82f976be 82a892ea 87d4e4a0 00000000 nt!PnpStartDevice+0x184
09 8078b7fc 82f96b27 00000012 00000000 87d4e4a0 nt!PnpStartDeviceNode+0x2a6
0a 8078b818 82f8b0b8 00000000 00000000 00000000 nt!PipProcessStartPhase1+0x87
0b 8078ba14 82a87f83 87df5748 00000000 8078ba50 nt!PipProcessDevNodeTree+0x1cc
0c 8078ba5c 82a87e4a 00000000 87d4cab8 00000000 nt!PnpDeviceActionWorker+0x129
0d 8078ba74 832b06ba 00000000 00000000 00000000 nt!PnpRequestDeviceAction+0x15e
0e 8078baec 832ad52b 8080f8c0 00000000 80810e50 nt!IopInitializeBootDrivers+0x414
0f 8078bb6c 832a29b7 8080f8c0 87de25e0 87de2270 nt!IoInitSystem+0x593
10 8078bc4c 82ecd012 8078bc90 8313d0da 8080f8c0 nt!Phase1InitializationDiscard+0xd67
11 8078bc54 8313d0da 8080f8c0 0010209f 00000000 nt!Phase1Initialization+0xd
12 8078bc90 82be6555 82ecd005 8080f8c0 00000000 nt!PspSystemThreadStartup+0x178
13 00000000 00000000 00000000 00000000 00000000 nt!KiThreadStartup+0x19
另外,調用堆棧Frame#06有個熟悉的函數調用:IofCallDriver,參數顯示的DeviceInst:ACPI_HAL\PNP0C08,它就是ACPI spec中指定ACPI.sys(OSPM)
kd> !devstack 87e535d8
!DevObj !DrvObj !DevExt ObjectName
87d4cc60 \Driver\ACPI 87e548a0
> 87e535d8 \Driver\ACPI_HAL 87e53690 0000006c
!DevNode 87d4b008 :
DeviceInst is "ACPI_HAL\PNP0C08\0"
ServiceName is "ACPI"
繼續往下調試,會看到OS從全局變量ACPI!AcpiInformation+0x04處獲得資訊,感覺ACPI!AcpiInformation是個結構體指針:
kd> p
ACPI!ACPIEnableEnterACPIMode+0x81:
88cdbd61 a194bbd088 mov eax,dword ptr [ACPI!AcpiInformation (88d0bb94)]
kd> u ACPI!ACPIEnableEnterACPIMode+0x81
8bf8 mov edi,eax
a1942bcf88 mov eax,dword ptr [ACPI!AcpiInformation (88cf2b94)]
8b4004 mov eax,dword ptr [eax+4] ;<---從結構體偏移取值
660fb64034 movzx ax,byte ptr [eax+34h] ;<---ACPI!AcpiInformation+4處取得的值仍然是結構體指針
0fb7c0 movzx eax,ax
ACPI!AcpiInformation結構體長成什麼樣?順帶一提,dt的輸出結果中從ACPI!_ACPIInformation!PM1a_BLK開始到ACPI!_ACPIInformation!GpeSize都是ACPI spec中的Fixed Hardware Register。
kd> dt _ACPIInformation
ACPI!_ACPIInformation
+0x000 RootSystemDescTable : Ptr32 _RSDT_32
+0x004 FixedACPIDescTable : Ptr32 _FADT
+0x008 FirmwareACPIControlStructure : Ptr32 _FACS
+0x00c DiffSystemDescTable : Ptr32 _DSDT
+0x010 MultipleApicTable : Ptr32 _MAPIC
+0x014 GlobalLock : Ptr32 Uint4B
+0x018 GlobalLockQueue : _LIST_ENTRY
+0x020 GlobalLockQueueLock : Uint4B
+0x024 GlobalLockOwnerContext : Ptr32 Void
+0x028 GlobalLockOwnerDepth : Uint4B
+0x02c ACPIOnly : UChar
+0x030 PM1a_BLK : Uint4B
+0x034 PM1b_BLK : Uint4B
+0x038 PM1a_CTRL_BLK : Uint4B
+0x03c PM1b_CTRL_BLK : Uint4B
+0x040 PM2_CTRL_BLK : Uint4B
+0x044 PM_TMR : Uint4B
+0x048 GP0_BLK : Uint4B
+0x04c GP0_ENABLE : Uint4B
+0x050 GP0_LEN : UChar
+0x052 Gpe0Size : Uint2B
+0x054 GP1_BLK : Uint4B
+0x058 GP1_ENABLE : Uint4B
+0x05c GP1_LEN : UChar
+0x05e Gpe1Size : Uint2B
+0x060 GP1_Base_Index : Uint2B
+0x062 GpeSize : Uint2B
+0x064 SMI_CMD : Uint4B
+0x068 pm1_en_bits : Uint2B
+0x06a pm1_wake_mask : Uint2B
+0x06c pm1_wake_status : Uint2B
+0x06e c2_latency : Uint2B
+0x070 c3_latency : Uint2B
+0x074 ACPI_Flags : Uint4B
+0x078 ACPI_Capabilities : Uint4B
+0x07c Dockable : UChar
再dump出ACPI!AcpiInformation->FixedACPIDescTable的記憶體,其中smi_cmd_io_port/acpi_on_value的值和前面截圖中RW中顯示的值一緻。
kd> dx -id 0,0,ffffffff87de2548 -r1 ((ACPI!_FADT *)0xffd07010)
((ACPI!_FADT *)0xffd07010) : 0xffd07010 [Type: _FADT *]
[+0x000] Header [Type: _DESCRIPTION_HEADER]
[+0x024] facs : 0x3fefffc0 [Type: unsigned long]
[+0x028] dsdt : 0x3fee1652 [Type: unsigned long]
[+0x02c] int_model : 0x0 [Type: unsigned char]
[+0x02d] pm_profile : 0x0 [Type: unsigned char]
[+0x02e] sci_int_vector : 0x9 [Type: unsigned short]
[+0x030] smi_cmd_io_port : 0xb2 [Type: unsigned long]
[+0x034] acpi_on_value : 0xf0 [Type: unsigned char]
[+0x035] acpi_off_value : 0xf1 [Type: unsigned char]
//busdrv\acpi\driver\shared\acpiio.h
#define WRITE_ACPI_REGISTER(AcpiReg, Register, Value) ((*AcpiWriteRegisterRoutine)((AcpiReg), (Register), (Value)))
//busdrv\acpi\driver\shared\acpiio.c:
PWRITE_ACPI_REGISTER AcpiWriteRegisterRoutine = DefWriteAcpiRegister;
VOID
DefWriteAcpiRegister(
ACPI_REG_TYPE AcpiReg,
ULONG Register,
USHORT Value
)
/*++
Routine Description:
Write to the specified ACPI fixed register.
Arguments:
AcpiReg - Specifies which ACPI fixed register to write to.
Register - Specifies which GP register to write to. Not used for PM1x
registers.
Value - Data to write.
Return Value:
None.
--*/
{
switch (AcpiReg) {
..
case SMI_CMD:
WRITE_PORT_UCHAR((PUCHAR)AcpiInformation->SMI_CMD, (UCHAR)Value);
break;
default:
break;
}
}