如何从独立环境中关闭计算机? [英] How to power down the computer from a freestanding environment?

查看:12
本文介绍了如何从独立环境中关闭计算机?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在制作一个基于英特尔 x86 架构的保护模式操作系统,并且正在寻找一些关于如何通过汇编代码或类似的东西关闭计算机电源的信息.你能帮我解决这个问题吗?

解决方案

来自 http:///forum.osdev.org/viewtopic.php?t=16990

ACPI 关闭在技术上是一件非常简单的事情,只需要一个 outw(PM1a_CNT, SLP_TYPa | SLP_EN );并且计算机已关闭.问题在于这些值的收集,尤其是因为 SLP_TYPa 位于 DSDT 中的 _S5 对象中,因此进行了 AML 编码.

下面是在哪里可以找到这些字段的简单地图".

<上一页>RSD PTR"||偏移量 16 处的 RsdtAddress 指针||/RSDT"||偏移量 36 + 4 * n 处的指针(检查 sig "FACP" 的目标以获得正确的 n)||/FACP"||||======||||||PM1a_CNT_BLK;偏移量:64(参见第 4.7.3.2 节)||PM1b_CNT_BLK;偏移量:68||||||/||SLP_TYPx;位 10-12||SLP_EN;位 13||偏移量 40 处的 DSDT 指针||/DSDT"(以某种方式导出 \_S5 对象.)

要导出 \_S5 对象,通常会使用 AML 解释器,但考虑到我们正在构建一个爱好操作系统,这显然不是一种选择.简单的解决方案是手动扫描 DSDT.AML 语言规定 _... 对象只定义一次,这使得查找 \_S5 对象变得非常简单,因为一个简单的 memcmp() 就足够了.一旦找到 SLP_TYPx 值就会被提取出来.

<上一页>\_S5 对象的字节码-----------------------------------------|(可选)||||名称OP | |_ |小号 |5 |_08 |5A |5F |53 |35 |5F------------------------------------------------------------------------------------------------------------|||( SLP_TYPa ) |(SLP_TYPb) |(保留) |(预订的 )包OP |包装长度 |数字元素 |字节前缀编号 |字节前缀编号 |字节前缀编号 |字节前缀编号12 |0A |04 |0A 05 |0A 05 |0A 05 |0A 05----这个结构也见过----------包OP |包装长度 |数字元素 |12 |06 |04 |00 00 00 00

信息的收集最好在操作系统初始化时进行,因为之后您可以重复使用 ram 而不必担心损坏它.

现在剩下的就是 outw(PM1a_CNT, SLP_TYPa | SLP_EN ); 你走了.如果 PM1b_CNT != 0 你需要用 b 重复它.

如果这有点太抽象,这里有一些代码可以查看

<代码>////这里是稍微复杂的 ACPI poweroff 代码//#include <stddef.h>#include <print.h>#include <string.h>#include <io.h>#include <time.h>双字 *SMI_CMD;字节 ACPI_ENABLE;字节 ACPI_DISABLE;双字 *PM1a_CNT;双字*PM1b_CNT;字 SLP_TYPa;字 SLP_TYPb;字 SLP_EN;字SCI_EN;字节 PM1_CNT_LEN;结构体 RSDPtr{字节签名[8];字节校验和;字节 OemID[6];字节修订;dword *RsdtAddress;};结构 FACP{字节签名[4];双字长度;字节 unneded1[40 - 8];双字*DSDT;字节 unneded2[48 - 44];双字 *SMI_CMD;字节 ACPI_ENABLE;字节 ACPI_DISABLE;字节 unneded3[64 - 54];双字*PM1a_CNT_BLK;双字*PM1b_CNT_BLK;字节 unneded4[89 - 72];字节 PM1_CNT_LEN;};//检查给定地址是否具有有效的标头无符号整数 *acpiCheckRSDPtr(无符号整数 *ptr){char *sig = "RSD PTR";结构 RSDPtr *rsdp = (结构 RSDPtr *) ptr;字节 *bptr;字节检查 = 0;诠释我;if (memcmp(sig, rsdp, 8) == 0){//检查校验和 rsdpdbptr = (字节 *) ptr;for (i=0; iRevision == 0)wrstr("acpi 1");别的wrstr("acpi 2");*/return (unsigned int *) rsdp->RsdtAddress;}}返回空值;}//找到 acpi 头并返回 rsdt 的地址无符号整数 *acpiGetRSDPtr(void){无符号整数 * 地址;无符号整数 *rsdp;//在 1mb 标记以下搜索 RSDP 签名for (addr = (unsigned int *) 0x000E0000; (int) addr<0x00100000; addr += 0x10/sizeof(addr)){rsdp = acpiCheckRSDPtr(addr);如果(rsdp!= NULL)返回rsdp;}//地址 0x40:0x0E 是 ebda 的 RM 段int ebda = *((短 *) 0x40E);//获取指针ebda = ebda*0x10 &0x000FFFFF;//将段转换为线性地址//为根系统描述指针签名搜索扩展 BIOS 数据区for (addr = (unsigned int *) ebda; (int) addrDSDT +36;//跳过标题int dsdtLength = *(facp->DSDT+1) -36;而 (0  0){//检查有效的 AML 结构if ( ( *(S5Addr-1) == 0x08 || ( *(S5Addr-2) == 0x08 && *(S5Addr-1) == '\') ) && *(S5Addr+4) == 0x12){S5Addr += 5;S5Addr += ((*S5Addr &0xC0)>>6) +2;//计算 PkgLength 大小如果(*S5Addr == 0x0A)S5地址++;//跳过字节前缀SLP_TYPa = *(S5Addr)<<10;S5地址++;如果(*S5Addr == 0x0A)S5地址++;//跳过字节前缀SLP_TYPb = *(S5Addr)<<10;SMI_CMD = facp->SMI_CMD;ACPI_ENABLE = facp->ACPI_ENABLE;ACPI_DISABLE = facp->ACPI_DISABLE;PM1a_CNT = facp->PM1a_CNT_BLK;PM1b_CNT = facp->PM1b_CNT_BLK;PM1_CNT_LEN = facp->PM1_CNT_LEN;SLP_EN = 1<<13;SCI_EN = 1;返回0;} 别的 {wrstr("\_S5 解析错误.
");}} 别的 {wrstr("\_S5 不存在.
");}} 别的 {wrstr("DSDT 无效.
");}}指针++;}wrstr("没有有效的 FACP 存在.
");} 别的 {wrstr("没有 acpi.
");}返回-1;}无效 acpiPowerOff(无效​​){//如果 acpi 可以关闭,则 SCI_EN 设置为 1如果(SCI_EN == 0)返回;acpiEnable();//发送关机命令outw((unsigned int) PM1a_CNT, SLP_TYPa | SLP_EN );如果(PM1b_CNT!= 0)outw((unsigned int) PM1b_CNT, SLP_TYPb | SLP_EN );wrstr("acpi 关机失败.
");}

有关更多信息,请阅读 ACPI 1.0a 规范的相应部分

<上一页>9.1.7 从工作状态过渡到软关机状态7.5.2 \_Sx 状态7.4.1 \_S54.7.2.3 休眠/唤醒控制16.3 AML 字节流字节值16.2.3 包长度编码

这适用于我所有的机器 bochs 和 qemu.但我注意到不需要启用 ACPI 来让电脑关机.虽然我不知道是否总是这样.

如果你只是想玩一点.对于 bochs 和 qemu,它是 outw( 0xB004, 0x0 | 0x2000 );

I'm making a protected-mode OS based on Intel's x86 architecture, and was looking for some information on how to power off the computer via assembly code, or something like that. Could you help me with this problem?

解决方案

from http://forum.osdev.org/viewtopic.php?t=16990

The ACPI shutdown is technically a really simple thing all that is needed is a outw(PM1a_CNT, SLP_TYPa | SLP_EN ); and the computer is powered off. The problem lies in the gathering of these values especially since the SLP_TYPa is in the _S5 object which is in the DSDT and therefore AML encoded.

Below is a simple "map" of where to find these fields.

    "RSD PTR "
      ||
    RsdtAddress pointer at offset 16
      ||
      /
    "RSDT"
      ||
    pointer at offset 36 + 4 * n (check the target for the sig "FACP" to get the right n)
      ||
      /
    "FACP"
      ||
      ||=====
      ||   ||
      ||   PM1a_CNT_BLK; offset: 64   (see section 4.7.3.2)
      ||   PM1b_CNT_BLK; offset: 68
      ||      ||
      ||      /
      ||      SLP_TYPx; bit 10-12
      ||      SLP_EN;     bit 13
      ||
    DSDT pointer at offset 40
      ||
      /
    "DSDT"   (export the \_S5 object somehow.)

To export the \_S5 object one would normally use an AML interpreter but that's obviously not an option considering we're building a hobby OS. The simple solution is to scan the DSDT manually. The AML language specifies that _... objects are defined only once which makes it very simple to find the \_S5 object since a simple memcmp() is enough. Once found the SLP_TYPx values are extracted.

    bytecode of the \_S5 object
    -----------------------------------------
            | (optional) |    |    |    |
    NameOP |           | _  | S  | 5  | _
    08     | 5A         | 5F | 53 | 35 | 5F

    -----------------------------------------------------------------------------------------------------------
               |           |              | ( SLP_TYPa   ) | ( SLP_TYPb   ) | ( Reserved   ) | (Reserved    )
    PackageOP | PkgLength | NumElements  | byteprefix Num | byteprefix Num | byteprefix Num | byteprefix Num
    12        | 0A        | 04           | 0A         05  | 0A          05 | 0A         05  | 0A         05

    ----this-structure-was-also-seen----------------------
    PackageOP | PkgLength | NumElements |
    12        | 06        | 04          | 00 00 00 00

The gathering of the information is best performed at OS initialization because after that you can reuse the ram and don't need to worry about corrupting it.

Now all that remains is outw(PM1a_CNT, SLP_TYPa | SLP_EN ); and you're gone. If PM1b_CNT != 0 you need to repeat it with b.

If that was a little too abstract here is some code to look at

//
// here is the slighlty complicated ACPI poweroff code
//

#include <stddef.h>
#include <print.h>
#include <string.h>
#include <io.h>
#include <time.h>



dword *SMI_CMD;
byte ACPI_ENABLE;
byte ACPI_DISABLE;
dword *PM1a_CNT;
dword *PM1b_CNT;
word SLP_TYPa;
word SLP_TYPb;
word SLP_EN;
word SCI_EN;
byte PM1_CNT_LEN;



struct RSDPtr
{
   byte Signature[8];
   byte CheckSum;
   byte OemID[6];
   byte Revision;
   dword *RsdtAddress;
};



struct FACP
{
   byte Signature[4];
   dword Length;
   byte unneded1[40 - 8];
   dword *DSDT;
   byte unneded2[48 - 44];
   dword *SMI_CMD;
   byte ACPI_ENABLE;
   byte ACPI_DISABLE;
   byte unneded3[64 - 54];
   dword *PM1a_CNT_BLK;
   dword *PM1b_CNT_BLK;
   byte unneded4[89 - 72];
   byte PM1_CNT_LEN;
};



// check if the given address has a valid header
unsigned int *acpiCheckRSDPtr(unsigned int *ptr)
{
   char *sig = "RSD PTR ";
   struct RSDPtr *rsdp = (struct RSDPtr *) ptr;
   byte *bptr;
   byte check = 0;
   int i;

   if (memcmp(sig, rsdp, 8) == 0)
   {
      // check checksum rsdpd
      bptr = (byte *) ptr;
      for (i=0; i<sizeof(struct RSDPtr); i++)
      {
         check += *bptr;
         bptr++;
      }

      // found valid rsdpd   
      if (check == 0) {
         /*
          if (desc->Revision == 0)
            wrstr("acpi 1");
         else
            wrstr("acpi 2");
         */
         return (unsigned int *) rsdp->RsdtAddress;
      }
   }

   return NULL;
}



// finds the acpi header and returns the address of the rsdt
unsigned int *acpiGetRSDPtr(void)
{
   unsigned int *addr;
   unsigned int *rsdp;

   // search below the 1mb mark for RSDP signature
   for (addr = (unsigned int *) 0x000E0000; (int) addr<0x00100000; addr += 0x10/sizeof(addr))
   {
      rsdp = acpiCheckRSDPtr(addr);
      if (rsdp != NULL)
         return rsdp;
   }


   // at address 0x40:0x0E is the RM segment of the ebda
   int ebda = *((short *) 0x40E);   // get pointer
   ebda = ebda*0x10 &0x000FFFFF;   // transform segment into linear address

   // search Extended BIOS Data Area for the Root System Description Pointer signature
   for (addr = (unsigned int *) ebda; (int) addr<ebda+1024; addr+= 0x10/sizeof(addr))
   {
      rsdp = acpiCheckRSDPtr(addr);
      if (rsdp != NULL)
         return rsdp;
   }

   return NULL;
}



// checks for a given header and validates checksum
int acpiCheckHeader(unsigned int *ptr, char *sig)
{
   if (memcmp(ptr, sig, 4) == 0)
   {
      char *checkPtr = (char *) ptr;
      int len = *(ptr + 1);
      char check = 0;
      while (0<len--)
      {
         check += *checkPtr;
         checkPtr++;
      }
      if (check == 0)
         return 0;
   }
   return -1;
}



int acpiEnable(void)
{
   // check if acpi is enabled
   if ( (inw((unsigned int) PM1a_CNT) &SCI_EN) == 0 )
   {
      // check if acpi can be enabled
      if (SMI_CMD != 0 && ACPI_ENABLE != 0)
      {
         outb((unsigned int) SMI_CMD, ACPI_ENABLE); // send acpi enable command
         // give 3 seconds time to enable acpi
         int i;
         for (i=0; i<300; i++ )
         {
            if ( (inw((unsigned int) PM1a_CNT) &SCI_EN) == 1 )
               break;
            sleep(10);
         }
         if (PM1b_CNT != 0)
            for (; i<300; i++ )
            {
               if ( (inw((unsigned int) PM1b_CNT) &SCI_EN) == 1 )
                  break;
               sleep(10);
            }
         if (i<300) {
            wrstr("enabled acpi.
");
            return 0;
         } else {
            wrstr("couldn't enable acpi.
");
            return -1;
         }
      } else {
         wrstr("no known way to enable acpi.
");
         return -1;
      }
   } else {
      //wrstr("acpi was already enabled.
");
      return 0;
   }
}

//
// bytecode of the \_S5 object
// -----------------------------------------
//        | (optional) |    |    |    |   
// NameOP |           | _  | S  | 5  | _
// 08     | 5A         | 5F | 53 | 35 | 5F
//
// -----------------------------------------------------------------------------------------------------------
//           |           |              | ( SLP_TYPa   ) | ( SLP_TYPb   ) | ( Reserved   ) | (Reserved    )
// PackageOP | PkgLength | NumElements  | byteprefix Num | byteprefix Num | byteprefix Num | byteprefix Num
// 12        | 0A        | 04           | 0A         05  | 0A          05 | 0A         05  | 0A         05
//
//----this-structure-was-also-seen----------------------
// PackageOP | PkgLength | NumElements |
// 12        | 06        | 04          | 00 00 00 00
//
// (Pkglength bit 6-7 encode additional PkgLength bytes [shouldn't be the case here])
//
int initAcpi(void)
{
   unsigned int *ptr = acpiGetRSDPtr();

   // check if address is correct  ( if acpi is available on this pc )
   if (ptr != NULL && acpiCheckHeader(ptr, "RSDT") == 0)
   {
      // the RSDT contains an unknown number of pointers to acpi tables
      int entrys = *(ptr + 1);
      entrys = (entrys-36) /4;
      ptr += 36/4;   // skip header information

      while (0<entrys--)
      {
         // check if the desired table is reached
         if (acpiCheckHeader((unsigned int *) *ptr, "FACP") == 0)
         {
            entrys = -2;
            struct FACP *facp = (struct FACP *) *ptr;
            if (acpiCheckHeader((unsigned int *) facp->DSDT, "DSDT") == 0)
            {
               // search the \_S5 package in the DSDT
               char *S5Addr = (char *) facp->DSDT +36; // skip header
               int dsdtLength = *(facp->DSDT+1) -36;
               while (0 < dsdtLength--)
               {
                  if ( memcmp(S5Addr, "_S5_", 4) == 0)
                     break;
                  S5Addr++;
               }
               // check if \_S5 was found
               if (dsdtLength > 0)
               {
                  // check for valid AML structure
                  if ( ( *(S5Addr-1) == 0x08 || ( *(S5Addr-2) == 0x08 && *(S5Addr-1) == '\') ) && *(S5Addr+4) == 0x12 )
                  {
                     S5Addr += 5;
                     S5Addr += ((*S5Addr &0xC0)>>6) +2;   // calculate PkgLength size

                     if (*S5Addr == 0x0A)
                        S5Addr++;   // skip byteprefix
                     SLP_TYPa = *(S5Addr)<<10;
                     S5Addr++;

                     if (*S5Addr == 0x0A)
                        S5Addr++;   // skip byteprefix
                     SLP_TYPb = *(S5Addr)<<10;

                     SMI_CMD = facp->SMI_CMD;

                     ACPI_ENABLE = facp->ACPI_ENABLE;
                     ACPI_DISABLE = facp->ACPI_DISABLE;

                     PM1a_CNT = facp->PM1a_CNT_BLK;
                     PM1b_CNT = facp->PM1b_CNT_BLK;

                     PM1_CNT_LEN = facp->PM1_CNT_LEN;

                     SLP_EN = 1<<13;
                     SCI_EN = 1;

                     return 0;
                  } else {
                     wrstr("\_S5 parse error.
");
                  }
               } else {
                  wrstr("\_S5 not present.
");
               }
            } else {
               wrstr("DSDT invalid.
");
            }
         }
         ptr++;
      }
      wrstr("no valid FACP present.
");
   } else {
      wrstr("no acpi.
");
   }

   return -1;
}



void acpiPowerOff(void)
{
   // SCI_EN is set to 1 if acpi shutdown is possible
   if (SCI_EN == 0)
      return;

   acpiEnable();

   // send the shutdown command
   outw((unsigned int) PM1a_CNT, SLP_TYPa | SLP_EN );
   if ( PM1b_CNT != 0 )
      outw((unsigned int) PM1b_CNT, SLP_TYPb | SLP_EN );

   wrstr("acpi poweroff failed.
");
}

For further information read the corresponding sections of the ACPI 1.0a specification

    9.1.7   Transitioning from the Working to the Soft Off State
    7.5.2   \_Sx states
    7.4.1   \_S5
    4.7.2.3    Sleeping/Wake Control

    16.3   AML Byte Streeam Byte Values
    16.2.3   Package Length Encoding

This works on all of my machines bochs and qemu. but I noticed that one needn't enable ACPI for the pc to power down. Though i don't know if this is always the case.

If you just want to play a little. For bochs and qemu it's outw( 0xB004, 0x0 | 0x2000 );

这篇关于如何从独立环境中关闭计算机?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆