ACPI poweroff

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
kaworu
Posts: 4
Joined: Tue May 13, 2008 2:55 am
Location: Germany

ACPI poweroff

Post by kaworu »

I have written a "simple" acpi poweroff routine and wanted to share my knowledge since it seems here isn't that much acpi related stuff available.
Maybe this could be added to the wiki?

###################################

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 especialy 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.

Code: Select all

"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 thats 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.

Code: Select all

 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 gatering of the informations 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 your'e gone.
If PM1b_CNT is != 0 you need to repeat it with b.

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

Code: Select all

//
// 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

Code: Select all

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 my machines bochs and qemu.
but i noticed that one needn't enable acpi for the pc to power down. Tough i dont know if this is always the case.

If you yust want to play a little.
For bochs and qemu it's outw( 0xB004, 0x0 | 0x2000 );
Cognition
Member
Member
Posts: 191
Joined: Tue Apr 15, 2008 6:37 pm
Location: Gotham, Batmanistan

Post by Cognition »

Technically I think there's one method you're suppose to execute first as well \_PTS. There's an optional method \_GTS too, but I think what you're describing should be fine for an emulator. Method execution would definitely require the use of interpreter as well. I don't know if a real system would require you todo an SMI->OS handoff of the resources first as well.
kaworu
Posts: 4
Joined: Tue May 13, 2008 2:55 am
Location: Germany

Post by kaworu »

youre right the _PTS should be executed but as far as i could see that only makes a aml local? thing and for \_S4 some pci things. so dosnt realy matter. me thinks
and actually all of my real hardware even powers down when acpi wasnt even enabled so there seems to be no problem. though maybe there a lot of machines where this dosnt apply
User avatar
lukem95
Member
Member
Posts: 536
Joined: Fri Aug 03, 2007 6:03 am
Location: Cambridge, UK

Post by lukem95 »

That looks very useful, do you think it could be added to the wiki?
~ Lukem95 [ Cake ]
Release: 0.08b
Image
User avatar
piranha
Member
Member
Posts: 1391
Joined: Thu Dec 21, 2006 7:42 pm
Location: Unknown. Momentum is pretty certain, however.
Contact:

Post by piranha »

Say, is this supposed to work in QEMU, or is there a flag I need to set in the Qemu command? Or am I just calling the enable function wrong?

Code: Select all

initAcpi();
Is all you need, right?

-JL
SeaOS: Adding VT-x, networking, and ARM support
dbittman on IRC, @danielbittman on twitter
https://dbittman.github.io
kaworu
Posts: 4
Joined: Tue May 13, 2008 2:55 am
Location: Germany

Post by kaworu »

No initAcpi();just retrieves everything needed and stores it so that you can use the ram used by acpi its basically a function you want to add into your kernel initialization the actual shutdown is performed by a later call to acpiPowerOff();

And i am okay with it being added to the wiki actually i strove to write it in a fashion which would enable a simple copy and paste. Hope i was able to acomplish it.
User avatar
piranha
Member
Member
Posts: 1391
Joined: Thu Dec 21, 2006 7:42 pm
Location: Unknown. Momentum is pretty certain, however.
Contact:

Post by piranha »

Yeah, I meant just to enable ACPI or start it or whatever, but the Init call doesn't work, it returns 'no acpi'. Is this a problem only in Qemu?

-JL
SeaOS: Adding VT-x, networking, and ARM support
dbittman on IRC, @danielbittman on twitter
https://dbittman.github.io
kaworu
Posts: 4
Joined: Tue May 13, 2008 2:55 am
Location: Germany

Post by kaworu »

You usually get the no acpi message when the RSDT wasnt found which is the case when acpi isnt available tough a disabled a20 gate could also be the cause since the header may lay in an area above 1m

its certainly not a qemu problem as it works in my qemu.
i have qemu v0.9.1 tough i didnt compile it myself but used the default ubuntu hardy package.
User avatar
piranha
Member
Member
Posts: 1391
Joined: Thu Dec 21, 2006 7:42 pm
Location: Unknown. Momentum is pretty certain, however.
Contact:

Post by piranha »

Well, it doesn't work for me. Maybe it's my memcmp function, thats the only one I didn't have already:

Code: Select all

int memcmp(char *s1, char *s2, int n)
{
	int i;
	for(i=0;i<n;i++)
	{
		if(s1[n] != s2[n])
			return 1;
	}
	return 0;
}
Does it need more or is it just completly messed up?

-JL
SeaOS: Adding VT-x, networking, and ARM support
dbittman on IRC, @danielbittman on twitter
https://dbittman.github.io
User avatar
Brynet-Inc
Member
Member
Posts: 2426
Joined: Tue Oct 17, 2006 9:29 pm
Libera.chat IRC: brynet
Location: Canada
Contact:

Post by Brynet-Inc »

piranha wrote:Well, it doesn't work for me. Maybe it's my memcmp function, thats the only one I didn't have already:

Code: Select all

int memcmp(char *s1, char *s2, int n)
{
	int i;
	for(i=0;i<n;i++)
	{
		if(s1[n] != s2[n])
			return 1;
	}
	return 0;
}
Does it need more or is it just completly messed up?

-JL
That's not even a standards compliant implementation.. heck, it's not even a working implementation.

Do you even read standards and stress test the functions you write? :roll:
Image
Twitter: @canadianbryan. Award by smcerm, I stole it. Original was larger.
User avatar
piranha
Member
Member
Posts: 1391
Joined: Thu Dec 21, 2006 7:42 pm
Location: Unknown. Momentum is pretty certain, however.
Contact:

Post by piranha »

That one I wrote quickly because I had about 5 min before I had to leave and I did want to test the ACPI implementation, so I didn't bother spending a while researching it, but I'll do that now.

EDIT: Oops, yeah, that was the problem, sorry about these posts, they were stupid.

-JL :oops:
SeaOS: Adding VT-x, networking, and ARM support
dbittman on IRC, @danielbittman on twitter
https://dbittman.github.io
frazzledjazz
Posts: 7
Joined: Tue Mar 03, 2009 3:52 pm

Re: ACPI poweroff

Post by frazzledjazz »

can someone check the above code to this please?

I have that code above already and need it in 32bits assembler or 32bits pascal for FPC. I THINK I ported it correctly.
The assembler cannot be inline, this is a FPC limitation, however I can accept either ATT or intel styles.

All examples I otherwise have are 16bit calls.[which wont work with FPC] I have yet to see any real world examples on dropping down to real mode for shutdown.

Code: Select all

//

// here is the slighlty complicated ACPI poweroff code

//


uses
	x86,strings;




type





record RSDPtr begin

  Signature:byte=8;

  CheckSum:byte;

  OemID:byte=6;

  Revision:byte;

  ^RsdtAddress:dword;

end;







record FACP begin

  Signature:byte=4;

  Length:dword;

  unneded1:byte=32;

  ^DSDT:dword;

  unneded2:byte=4;

  ^SMI_CMD:dword;

  ACPI_ENABLE:byte;

  ACPI_DISABLE:byte;

  unneded3:byte=10;

  ^PM1a_CNT_BLK:dword;

  ^PM1b_CNT_BLK:dword;

  unneded4:byte=67;

  PM1_CNT_LEN:byte;

end;



var


^SMI_CMD:dword;

ACPI_ENABLE:byte;

ACPI_DISABLE:byte;

^PM1a_CNT:dword;

^PM1b_CNT:dword;

SLP_TYPa:word;

SLP_TYPb:word;

SLP_EN:word;

SCI_EN:word;

PM1_CNT_LEN:byte;



// check if the given address has a valid header

function ^acpiCheckRSDPtr(^ptr:integer);


  ^sig:string;  
  rsdp:rsdPtr;

  bptr:integer;

  check:byte;

  i:integer;



begin
  ^sig:="RSD PTR";
  rsdp := ^ ptr;
  check:=0;

  rsdp:=^ptr
  if (memcmp(sig, rsdp, 8) = 0) then begin 

    // check checksum rsdpd

    bptr := ptr;
    i:=0;  

    repeat 

      check += ^bptr; //huh??

      inc(bptr);
      inc(i);

    until sizeof(rsdp);)  >i



    // found valid rsdpd

    if (check == 0) {

      /*

       if (desc->Revision == 0)

         wrstr("acpi 1");

      else

         wrstr("acpi 2");

      */

      return rsdp->RsdtAddress;

    end;

  end;



  return NULL;

end;







// finds the acpi header and returns the address of the rsdt

procedure ^acpiGetRSDPtr

 

   ^addr:integer;

   ^rsdp:integer;

begin



  // search below the 1mb mark for RSDP signature

    addr = 0x000E0000;
repeat 
    addr += 0x10/sizeof(addr));
{is this inc() or dec() im confused...}
 

    rsdp = acpiCheckRSDPtr(addr);

    if (rsdp != NULL)

      return rsdp;

until addr > 0x00100000;





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

end;







// checks for a given header and validates checksum

procedure acpiCheckHeader(^ptr:integer; ^sig:char); 

var
   ^checkPtr,len:integer;
   check:char;

begin

  if (memcmp(ptr, sig, 4) = 0) then begin

    ^checkPtr =  ptr;

    len = ^(ptr + 1);

    check = 0;

    while (len >0) do begin 

      check += ^checkPtr; //huh ??

      inc(checkPtr);

    end;

    if (check = 0)

      return 0;

  end;

  return -1;

end;







procedure acpiEnable; 

var
   i:integer;

begin
  // check if acpi is enabled

  if ((readportw (PM1a_CNT) and SCI_EN) = 0) then begin

    // check if acpi can be enabled

    if (SMI_CMD <> 0 & ACPI_ENABLE <> 0) then begin

      writeportb( SMI_CMD, ACPI_ENABLE); // send acpi enable command  --writeportb

      // give 3 seconds time to enable acpi

      i:=0;

      repeat

        if ((readportw( PM1a_CNT) and SCI_EN) = 1) then

          break;

        delay(3000);  //about 3 secs
         inc(i);

      until i > 300;

      if (PM1b_CNT <> 0) then

         i:=0;
      repeat

          if ((readportw( PM1b_CNT) and SCI_EN) = 1) then  //if readportw  () and ()=1

            break;

          delay(3000);
          inc(i);

      until i > 300;

      if (i>300) then begin

        writeln("enabled acpi.");

        return 0;

      end;
      else begin

        writeln("couldn"t enable acpi.");

        return -1;

      end;

    else begin

      writeln("no known way to enable acpi.\n");

      return -1;

    end;

  else begin

    writeln("acpi was already enabled.\n");

    return 0;

  end;

end;







//

// 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])

//

procedure initAcpi();
var
  ^ptr,entries,dsdtlength:integer;
  ^S5Addr,facp:pointer of char;
  
 begin

   ^ptr = acpiGetRSDPtr();

   

  // check if address is correct  ( if acpi is available on this pc )

  if (ptr <> NULL & acpiCheckHeader(ptr, "RSDT") = 0)then begin 

    // the RSDT contains an unknown number of pointers to acpi tables

    entrys = ^(ptr + 1);

    entrys = (entrys-36) /4;

    ptr += 36/4;   // skip header information



    while (entrys>0) do begin

      // looks like there is a decrement courter weirdly implemented here.
     // check if the desired table is reached

      if (acpiCheckHeader( ^ptr, "FACP") = 0) then begin

        entrys = -2;

        ^facp = ^ptr;  // i think this is right..


        if (acpiCheckHeader(facp->DSDT, "DSDT") = 0) then begin 

          // search the \_S5 package in the DSDT

          ^S5Addr = facp->DSDT +36; // skip header

          dsdtLength= ^(facp->DSDT+1) -36;

          while (dsdtLength >0) do begin 
 // again with the dec counter

            if (memcmp(S5Addr, "_S5_", 4) = 0)

              break;

            inc(S5Addr);

          }

          // check if \_S5 was found

          if (dsdtLength > 0) begin

            // check for valid AML structure

            if ((^(S5Addr-1) = 0x08 or {||} (^(S5Addr-2) == 0x08 and ^(S5Addr-1) = '\\')) and ^(S5Addr+4) = 0x12) then begin

              S5Addr += 5;

              S5Addr += ((^S5Addr &0xC0)>>6) +2;   // calculate PkgLength size   shr(6)?



              if (^S5Addr = 0x0A)

                inc(S5Addr);   // skip byteprefix

              SLP_TYPa = ^(S5Addr)<<10; // shl (10)??

              inc (S5Addr);



              if (^S5Addr = 0x0A)

                inc(S5Addr);   // skip byteprefix

              SLP_TYPb = ^(S5Addr)<<10; //shl(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;  // shl(13) ??

              SCI_EN = 1;



              return 0;

            end;
            else begin

              writeln("_S5 parse error.");

            end;

          end;
          else begin

            writeln("_S5 not present.");

          end;

        end;
        else begin

          writeln("DSDT invalid.");

        end;

      end;

      inc(ptr);

    end;

    writeln("no valid FACP present.");

  end;
  else begin

    writeln("no acpi.");

  end;



  return -1;

end;







procedure acpiPowerOff;

 begin

  // SCI_EN is set to 1 if acpi shutdown is possible

  if (SCI_EN = 0)

    return;



  acpiEnable();



  // send the shutdown command

  writeportw(PM1a_CNT, SLP_TYPa | SLP_EN); // what the hell does | (single pipe) mean? there can be only ONE command.
//IE: writeport[b,w,l](port num ,byte[or word])


  if (PM1b_CNT <> 0)

    writeportw( PM1b_CNT, SLP_TYPb | SLP_EN);



  writeln("acpi poweroff failed.");

end;

end.

jal
Member
Member
Posts: 1385
Joined: Wed Oct 31, 2007 9:09 am

Re: ACPI poweroff

Post by jal »

frazzledjazz wrote:can someone check the above code to this please?
I think you've been told before, but we are not here to do your homework. Also, please stop copy/pasting with double linewidth, it's quite annoying!


JAL
User avatar
andymc
Member
Member
Posts: 31
Joined: Tue Feb 01, 2011 6:18 pm
Location: London, UK
Contact:

Re: ACPI poweroff

Post by andymc »

Sorry to revive an old thread, but I found this really useful and I have a couple of questions.

For my operating system, Visopsys, I was really only interested in implementing ACPI power off for the time being, but I started reading the spec and implementing the basic stuff, gathering the structures, etc., then I hit the namespace stuff and the bytecode stuff and my head just started spinning. I haven't seen a spec this computer science wanker-ish since I read the USB human interface spec (and I say that as a CompSci guy). This post really helped distill the essential elements, thanks.

I like to give credit when I follow an example like this (normally full name, email address) - can you tell me how I should credit this?

Now to the questions:

On my laptop system, it successfully powers off to the soft-off state, but when the power button is subsequently pressed, all I see is a small cursor, and it requires a hard reset to boot normally. It's almost as if the system expects to wake from that state. Is there something (such as the wakeup vector) that I need to clear, in order to prevent this? Could this relate to the things Cognition mentioned?

Second, on the same laptop system, enabling ACPI as shown seems to interfere with other devices. It almost looks as if there are unserviced interrupts causing problems with (for example) my ATA driver. Should I be implementing an interrupt handler to at least acknowledge any ACPI event interrupts?

Cheers for any help
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re: ACPI poweroff

Post by Brendan »

Hi,
andymc wrote:On my laptop system, it successfully powers off to the soft-off state, but when the power button is subsequently pressed, all I see is a small cursor, and it requires a hard reset to boot normally. It's almost as if the system expects to wake from that state. Is there something (such as the wakeup vector) that I need to clear, in order to prevent this? Could this relate to the things Cognition mentioned?
Cognition is right, but that's only the beginning. You'd have to decipher the entire ACPI specification to get a full picture of how many different ways kaworu's code can break in lots of different situations. For example, the "\_S5" method alone could call a bunch of different methods elsewhere, and can even do different things depending on the current state, or which OS you told ACPI you are, or whatever; and none of that is taken into account. You need a full AML interpreter. You can't extract a few values and use them directly and hope that will work.
andymc wrote:Second, on the same laptop system, enabling ACPI as shown seems to interfere with other devices. It almost looks as if there are unserviced interrupts causing problems with (for example) my ATA driver. Should I be implementing an interrupt handler to at least acknowledge any ACPI event interrupts?
In "default mode" the firmware takes care of all the power management stuff for you. This is like having a chauffeur drive your car - you get less control over how things are done, but don't need to worry about the details. When you enable ACPI you're effectively telling the firmware that you will take control over all the power management stuff (with the assistance of the AML). This is like telling the chauffeur that you'll drive yourself (with the assistance of a TomTom navigation system or something). When you enable ACPI and don't take control of all the power management stuff then things go wrong. This is like a car rolling down the street with its cruise control on and nobody behind the wheel.

When any ACPI events occur, you have to execute the corresponding AML.


Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
Locked