Step to UEFI (141)Windows 重启变关机的实现

最近看到一篇文章【参考1】,提到了修改 ACPI可以做到Windows重启变成关机【参考1】,从原理上说,Windows实现重启的方法是从 ACPI Table的FADT Table中Reset_Reg 和RESET_VALUE中读取到做重启的Port和Value。当需要重启的时候,将 Value写入Port即可。这个事情很多年前我碰到过问题,那还是XP的时代,当时遇到的问题是无法正常 Restart系统,后来研究发现当时的 ACPI Table 写的是向EC 的Port写Command的方式进行重启(同一个Chipset上的BIOS我们尽量会重用),但是恰巧EC没有实现这个功能。最后修改为正常的向 0xCF9 写入 06 即可解决)。类似的,Windows关机也是从 ACPI 中取得Port然后写入特定值。
具体的Table 定义在 ACPI Spec 中:

1. 我们需要确定 RSD PTR
r2s1

2. 找到 Extended System Description Table (Signature 是 “XSDT”)
r2s2

3. 在 XSDT 中查找到 Fixed ACPI Description Table (Signature 是 “FADT”),其中的 RESET_REG金额 RESET_VALUE就是我们需要的
r2s3

4. 将 FADT 中的 RESET_REG 修改为Pm1aCnBlk+1,将RESET_VALUE修改为0x3C(例如,我们读取到的Pm1aCnBlk == 1804h,对这个Port写入 3C00h 能够实现关机操作,那么对 1805h写入 3Ch同样能够实现关机)。特别注意的是,RESET_REG 是一个结构体,其中的 RegisterBitWidth 需要设置为 18 (默认为8)
r2s4

5. 重新计算 FADT 的 Checksum 填写回去。

完整的代码:

/** @file
    A simple, basic, application showing how the Hello application could be
    built using the "Standard C Libraries" from StdLib.

    Copyright (c) 2010 - 2011, Intel Corporation. All rights reserved.<BR>
    This program and the accompanying materials
    are licensed and made available under the terms and conditions of the BSD License
    which accompanies this distribution. The full text of the license may be found at
    http://opensource.org/licenses/bsd-license.

    THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
    WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
**/
#include <Library/BaseLib.h>
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/PrintLib.h>
#include <Library/ShellCEntryLib.h>
#include <Library/BaseMemoryLib.h>
#include <Protocol/EfiShell.h>
#include <Library/ShellLib.h>

#include "acpi.h"
#include "acpi61.h"

extern EFI_BOOT_SERVICES         *gBS;
extern EFI_RUNTIME_SERVICES      *gRT;
extern EFI_SYSTEM_TABLE          *gST;


EFI_GUID        gEfiAcpi20TableGuid =   { 0x8868E871, 0xE4F1, 0x11D3, 
                        { 0xBC, 0x22, 0x00, 0x80, 0xC7, 0x3C, 0x88, 0x81 }};
//              
//Calcaulate a checksum
//              
void SetFADTChecksum(EFI_ACPI_6_1_FIXED_ACPI_DESCRIPTION_TABLE       *pFADT) {
        UINT8   c=0;
        UINT8   *p;
        UINT32  i;
        
        p=(UINT8*)pFADT;
        pFADT->Header.Checksum=0;
        for (i=0;i<pFADT->Header.Length;i++)    {
                c=c + *p;
                p++;
        }
        pFADT->Header.Checksum=0-c;
}

/***
  Demonstrates basic workings of the main() function by displaying a
  welcoming message.

  Note that the UEFI command line is composed of 16-bit UCS2 wide characters.
  The easiest way to access the command line parameters is to cast Argv as:
      wchar_t **wArgv = (wchar_t **)Argv;

  @param[in]  Argc    Number of argument tokens pointed to by Argv.
  @param[in]  Argv    Array of Argc pointers to command line tokens.

  @retval  0         The application exited normally.
  @retval  Other     An error occurred.
***/
INTN
EFIAPI
ShellAppMain (
  IN UINTN Argc,
  IN CHAR16 **Argv
  )
{
        EFI_STATUS      Status;
        EFI_ACPI_DESCRIPTION_HEADER                     *XSDT;
        EFI_ACPI_6_1_ROOT_SYSTEM_DESCRIPTION_POINTER    *RSDP;
        UINT8           *p;
        UINTN           Index;
        UINT64          *Entry;
        EFI_ACPI_6_1_FIXED_ACPI_DESCRIPTION_TABLE       *pFADT;
        
        //1. Find RSDP
        Status=EfiGetSystemConfigurationTable(&gEfiAcpi20TableGuid,(VOID**)&RSDP);
        if(EFI_ERROR(Status)) {
                Print(L"Can't find Acpi Table\n");
                return 0;
        }
        
        //2. Find XSDT
        Print(L"RSDP address [%X]\n",RSDP);
        Print(L"XSDT address [%X]\n",RSDP->XsdtAddress);
        XSDT=(EFI_ACPI_DESCRIPTION_HEADER*)RSDP->XsdtAddress;
        Print(L"XSDT information\n");
        p=(UINT8*)XSDT;
        
        //Show some DSDT information
        Print(L" Signature [%c%c%c%c]\n",*p,*(p+1),*(p+2),*(p+3));
        
        //3.Find entries
        Entry=(UINT64*)&XSDT[1];
        Print(L" Entry 0 @[0x%x]\n",Entry);
        for (Index=0;Index<(XSDT->Length-sizeof(EFI_ACPI_DESCRIPTION_HEADER))/8;Index++) {
           //Print(L" Entry [0x%x]",Index);
           p=(UINT8*)(*Entry);
           //You can show every signature here
           //Print(L" [%x][%c%c%c%c]\n",*Entry,*p,*(p+1),*(p+2),*(p+3));
           if ((*p=='F')&&(*(p+1)=='A')&&(*(p+2)=='C')&&(*(p+3)=='P')) {
                   pFADT=(EFI_ACPI_6_1_FIXED_ACPI_DESCRIPTION_TABLE*) p;
                   Print(L"  Found FADT @[0x%X]\n",*Entry);
                   Print(L"      ResetReg[0x%X]\n",pFADT->ResetReg.Address);
                   Print(L"    ResetValue[0x%X]\n",pFADT->ResetValue);
                   Print(L"   XPm1aCntBlk[0x%X]\n",pFADT->XPm1aCntBlk.Address);
                   Print(L" Changing table value\n");
                   pFADT->ResetReg.RegisterBitWidth=16;
                   pFADT->ResetReg.Address=pFADT->XPm1aCntBlk.Address+1;
                   pFADT->ResetValue=0x3C;
                   Print(L"      ResetReg[0x%X]\n",pFADT->ResetReg.Address);
                   Print(L"    ResetValue[0x%X]\n",pFADT->ResetValue);

                   SetFADTChecksum(pFADT);
           }
           Entry++;
        }
        return 0;
}

 

运行结果:
r2s5
执行这个 Application 之后,再Exit进入 Windows(不可以重启进入 Windows),用 RW 检查 Acpi Table 结果如下:
r2s6
最后,我们从菜单上选择 restart,会发现系统没有 reset 而是关机了。这样就实现了重启变关机。

完整的代码下载:
Reset2Shutdown

X64 Shell Application 下载:
r2s

参考:
1. http://blog.csdn.net/miss_lazygoat/article/details/48161645 ACPI table遍历并实现重启变关机

《Step to UEFI (141)Windows 重启变关机的实现》有6个想法

  1. 你好,博主。
    請教是否有研究過,在離開Windows時,BIOS可以透過什麼方法知道嗎?
    小弟目前拿了套KBL的源碼研究,進Windows時,會Enable ACPI(在AcpiModeEnable.c),
    但離開Windows時,郤不會進入Disable ACPI的函式中?

        1. 不好意思,这个问题很多年没接触过了。现在的架构和之前的差别蛮大的。我记得 ACPI 有一个 _SLP() 会 call 到,另外,还有 SMI 会出来。具体请查一下 IBV 的 code。

          此外,这个动作最后一步是 OS 根据 ACPI Table 给出的 IO Port 填写端口产生的,具体你可以参考这篇文章 http://www.lab-z.com/stu141r2s/。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注