如何从独立环境中关闭计算机电源?
我正在制作一个基于英特尔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是更新,更好的方法。