Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to power down the computer from a freestanding environment?

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?

like image 563
Carlos Avatar asked Jun 29 '10 23:06

Carlos


2 Answers

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.\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");
}

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 );

like image 119
Earlz Avatar answered Oct 14 '22 20:10

Earlz


APM

https://en.wikipedia.org/wiki/Advanced_Power_Management

Method tested on qemu-system-i386 2.0.0 Ubuntu 14.04:

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

For the exact compilation and running steps on QEMU, see this repo

osdev.org articles: http://wiki.osdev.org/Shutdown , http://wiki.osdev.org/APM

ACPI is the newer, better method.