我正在基于英特尔x86架构制作一个保护模式操作系统,想了解如何通过汇编代码或类似方法关闭计算机。您能帮助我解决这个问题吗?
可以使用汇编指令`ACPI`(高级配置和电源接口)来关闭计算机。具体而言,您可以在汇编代码中使用以下语句:``` mov eax, 0x2000 mov ebx, 0x0001 int 0x15 ```
这将调用BIOS中的ACPI函数,然后关机。
我正在基于英特尔x86架构制作一个保护模式操作系统,想了解如何通过汇编代码或类似方法关闭计算机。您能帮助我解决这个问题吗?
可以使用汇编指令`ACPI`(高级配置和电源接口)来关闭计算机。具体而言,您可以在汇编代码中使用以下语句:来自 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 || DSDT指针位于偏移量40处 || \/ "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
outw(PM1a_CNT, SLP_TYPa | SLP_EN );
,然后你就离开了。
如果PM1b_CNT != 0
,则需要使用b重复它。//
// 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.\n");
return 0;
} else {
wrstr("couldn't enable acpi.\n");
return -1;
}
} else {
wrstr("no known way to enable acpi.\n");
return -1;
}
} else {
//wrstr("acpi was already enabled.\n");
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.\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字节流字节值 16.2.3 包长度编码
这在我的所有机器上都可以使用bochs和qemu。 但我注意到电脑不需要启用ACPI就可以关机。虽然我不知道这是否总是这样。
如果您只想玩一下。
对于bochs和qemu,它是outw( 0xB004, 0x0 | 0x2000 );
APM
https://en.wikipedia.org/wiki/Advanced_Power_Management
在Ubuntu 14.04上测试了qemu-system-i386
2.0.0的方法:
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上编译和运行的确切步骤,请参阅此存储库
osdev.org文章:http://wiki.osdev.org/Shutdown,http://wiki.osdev.org/APM
ACPI是更新、更好的方法。