如何从独立环境中关闭计算机电源?

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

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

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

下面是一个简单的“地图”,找到这些字段的位置。

     “RSD PTR”
       ||
     RsdtAddress指针位于偏移量16处
       ||
       \ /
     “RSDT”
       ||
    偏移量为36 + 4 * n的指针(检查目标为“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对象的字节码
     -----------------------------------------
             |  (可选)|  |  |  |
     NameOP |  \ |  _ |  S |  5 |  _
     08 |  5A |  5F |  53 |  35 |  5F

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

     ----这种结构此结果也见过----------------------
     PackageOP |  PkgLength |  NumElements |
     12 |  06 |  04 |  00 00 00 00

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

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

如果这有点过于抽象,那么可以看一些代码

 // // here is the slighlty complicated ACPI poweroff code // #include  #include  #include  #include  #include  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; iRevision == 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) addrDSDT, "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.\n"); } } else { wrstr("\\_S5 not present.\n"); } } else { wrstr("DSDT invalid.\n"); } } ptr++; } wrstr("no valid FACP present.\n"); } else { wrstr("no acpi.\n"); } 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.\n"); } 

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

     9.1.7从工作状态转为软关闭状态
     7.5.2 \ _Sx状态
     7.4.1 \ _S5
     4.7.2.3睡眠/唤醒控制

     16.3 AML字节Streeam字节值
     16.2.3封装长度编码

这适用于我的所有机器bochs和qemu。 但我注意到一个人不需要启用ACPI来关闭电脑。 虽然我不知道是否总是如此。

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

qemu-system-i386 2.0.0 Ubuntu 14.04上测试的APM方法:

 mov $0x5301, %ax xor %bx, %bx int $0x15 /* Try to set apm version (to 1.2). */ mov $0x530e, %ax xor %bx, %bx mov $0x0102, %cx int $0x15 /* Turn off the system. */ mov $0x5307, %ax mov $0x0001, %bx mov $0x0003, %cx int $0x15 

有关QEMU的确切编译和运行步骤, 请参阅此repo

osdev.org文章: http ://wiki.osdev.org/Shutdown,http://wiki.osdev.org/APM

ACPI是更新,更好的方法。