david/ipxe
david
/
ipxe
Archived
1
0
Fork 0

[acpi] Add support for ACPI power off

Signed-off-by: Michael Brown <mcb30@ipxe.org>
This commit is contained in:
Michael Brown 2016-07-10 19:25:26 +01:00
parent 74222cd2c1
commit e19c0a8fd2
9 changed files with 519 additions and 4 deletions

View File

@ -4,6 +4,7 @@
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#define BDA_SEG 0x0040
#define BDA_EBDA 0x000e
#define BDA_EQUIPMENT_WORD 0x0010
#define BDA_FBMS 0x0013
#define BDA_REBOOT 0x0072

View File

@ -24,6 +24,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#define ERRFILE_int13con ( ERRFILE_ARCH | ERRFILE_CORE | 0x000d0000 )
#define ERRFILE_gdbmach ( ERRFILE_ARCH | ERRFILE_CORE | 0x000e0000 )
#define ERRFILE_rtc_entropy ( ERRFILE_ARCH | ERRFILE_CORE | 0x000f0000 )
#define ERRFILE_acpipwr ( ERRFILE_ARCH | ERRFILE_CORE | 0x00100000 )
#define ERRFILE_bootsector ( ERRFILE_ARCH | ERRFILE_IMAGE | 0x00000000 )
#define ERRFILE_bzimage ( ERRFILE_ARCH | ERRFILE_IMAGE | 0x00010000 )

View File

@ -0,0 +1,14 @@
#ifndef _IPXE_ACPIPWR_H
#define _IPXE_ACPIPWR_H
/** @file
*
* ACPI power off
*
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
extern int acpi_poweroff ( void );
#endif /* _IPXE_ACPIPWR_H */

View File

@ -0,0 +1,14 @@
#ifndef _IPXE_APM_H
#define _IPXE_APM_H
/** @file
*
* Advanced Power Management
*
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
extern int apm_poweroff ( void );
#endif /* _IPXE_APM_H */

View File

@ -0,0 +1,116 @@
/*
* Copyright (C) 2016 Michael Brown <mbrown@fensystems.co.uk>.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*
* You can also choose to distribute this program under the terms of
* the Unmodified Binary Distribution Licence (as given in the file
* COPYING.UBDL), provided that you have satisfied its requirements.
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#include <errno.h>
#include <byteswap.h>
#include <realmode.h>
#include <bios.h>
#include <ipxe/io.h>
#include <ipxe/acpi.h>
#include <ipxe/acpipwr.h>
/** @file
*
* ACPI power off
*
*/
/** Colour for debug messages */
#define colour FADT_SIGNATURE
/** _S5_ signature */
#define S5_SIGNATURE ACPI_SIGNATURE ( '_', 'S', '5', '_' )
/**
* Power off the computer using ACPI
*
* @ret rc Return status code
*/
int acpi_poweroff ( void ) {
struct acpi_fadt fadtab;
uint16_t ebda;
userptr_t rsdt;
userptr_t fadt;
unsigned int pm1a_cnt_blk;
unsigned int pm1b_cnt_blk;
unsigned int pm1a_cnt;
unsigned int pm1b_cnt;
unsigned int slp_typa;
unsigned int slp_typb;
int s5;
int rc;
/* Locate EBDA */
get_real ( ebda, BDA_SEG, BDA_EBDA );
/* Locate RSDT */
rsdt = acpi_find_rsdt ( real_to_user ( ebda, 0 ) );
if ( ! rsdt ) {
DBGC ( colour, "ACPI could not find RSDT (EBDA %04x)\n", ebda );
return -ENOENT;
}
/* Locate FADT */
fadt = acpi_find ( rsdt, FADT_SIGNATURE, 0 );
if ( ! fadt ) {
DBGC ( colour, "ACPI could not find FADT\n" );
return -ENOENT;
}
/* Read FADT */
copy_from_user ( &fadtab, fadt, 0, sizeof ( fadtab ) );
pm1a_cnt_blk = le32_to_cpu ( fadtab.pm1a_cnt_blk );
pm1b_cnt_blk = le32_to_cpu ( fadtab.pm1b_cnt_blk );
pm1a_cnt = ( pm1a_cnt_blk + ACPI_PM1_CNT );
pm1b_cnt = ( pm1b_cnt_blk + ACPI_PM1_CNT );
/* Extract \_S5 from DSDT or any SSDT */
s5 = acpi_sx ( rsdt, S5_SIGNATURE );
if ( s5 < 0 ) {
rc = s5;
DBGC ( colour, "ACPI could not extract \\_S5: %s\n",
strerror ( rc ) );
return rc;
}
/* Power off system */
if ( pm1a_cnt_blk ) {
slp_typa = ( ( s5 >> 0 ) & 0xff );
DBGC ( colour, "ACPI PM1a sleep type %#x => %04x\n",
slp_typa, pm1a_cnt );
outw ( ( ACPI_PM1_CNT_SLP_TYP ( slp_typa ) |
ACPI_PM1_CNT_SLP_EN ), pm1a_cnt );
}
if ( pm1b_cnt_blk ) {
slp_typb = ( ( s5 >> 8 ) & 0xff );
DBGC ( colour, "ACPI PM1b sleep type %#x => %04x\n",
slp_typb, pm1b_cnt );
outw ( ( ACPI_PM1_CNT_SLP_TYP ( slp_typb ) |
ACPI_PM1_CNT_SLP_EN ), pm1b_cnt );
}
DBGC ( colour, "ACPI power off failed\n" );
return -EPROTO;
}

View File

@ -32,14 +32,14 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#include <errno.h>
#include <realmode.h>
#include <ipxe/reboot.h>
#include <ipxe/apm.h>
/**
* Power off the computer using APM
*
* @ret rc Return status code
*/
static int apm_poweroff ( void ) {
int apm_poweroff ( void ) {
uint16_t apm_version;
uint16_t apm_signature;
uint16_t apm_flags;
@ -108,5 +108,3 @@ static int apm_poweroff ( void ) {
/* Should never happen */
return -ECANCELED;
}
PROVIDE_REBOOT ( pcbios, poweroff, apm_poweroff );

View File

@ -32,6 +32,8 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#include <ipxe/reboot.h>
#include <realmode.h>
#include <bios.h>
#include <ipxe/apm.h>
#include <ipxe/acpipwr.h>
/**
* Reboot system
@ -49,4 +51,24 @@ static void bios_reboot ( int warm ) {
__asm__ __volatile__ ( REAL_CODE ( "ljmp $0xf000, $0xfff0" ) );
}
/**
* Power off system
*
* @ret rc Return status code
*/
static int bios_poweroff ( void ) {
int rc;
/* Try APM */
if ( ( rc = apm_poweroff() ) != 0 )
DBG ( "APM power off failed: %s\n", strerror ( rc ) );
/* Try ACPI */
if ( ( rc = acpi_poweroff() ) != 0 )
DBG ( "ACPI power off failed: %s\n", strerror ( rc ) );
return rc;
}
PROVIDE_REBOOT ( pcbios, reboot, bios_reboot );
PROVIDE_REBOOT ( pcbios, poweroff, bios_poweroff );

View File

@ -24,6 +24,8 @@
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#include <errno.h>
#include <byteswap.h>
#include <ipxe/uaccess.h>
#include <ipxe/acpi.h>
#include <ipxe/interface.h>
@ -40,6 +42,22 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
******************************************************************************
*/
/**
* Transcribe ACPI table signature (for debugging)
*
* @v signature ACPI table signature
* @ret name ACPI table signature name
*/
static const char * acpi_name ( uint32_t signature ) {
static union {
uint32_t signature;
char name[5];
} u;
u.signature = cpu_to_le32 ( signature );
return u.name;
}
/**
* Fix up ACPI table checksum
*
@ -55,6 +73,262 @@ void acpi_fix_checksum ( struct acpi_description_header *acpi ) {
acpi->checksum -= sum;
}
/**
* Locate ACPI root system description table within a memory range
*
* @v start Start address to search
* @v len Length to search
* @ret rsdt ACPI root system description table, or UNULL
*/
static userptr_t acpi_find_rsdt_range ( userptr_t start, size_t len ) {
static const char signature[8] = RSDP_SIGNATURE;
struct acpi_rsdp rsdp;
userptr_t rsdt;
size_t offset;
uint8_t sum;
unsigned int i;
/* Search for RSDP */
for ( offset = 0 ; ( ( offset + sizeof ( rsdp ) ) < len ) ;
offset += RSDP_STRIDE ) {
/* Check signature and checksum */
copy_from_user ( &rsdp, start, offset, sizeof ( rsdp ) );
if ( memcmp ( rsdp.signature, signature,
sizeof ( signature ) ) != 0 )
continue;
for ( sum = 0, i = 0 ; i < sizeof ( rsdp ) ; i++ )
sum += *( ( ( uint8_t * ) &rsdp ) + i );
if ( sum != 0 )
continue;
/* Extract RSDT */
rsdt = phys_to_user ( le32_to_cpu ( rsdp.rsdt ) );
DBGC ( rsdt, "RSDT %#08lx found via RSDP %#08lx\n",
user_to_phys ( rsdt, 0 ),
user_to_phys ( start, offset ) );
return rsdt;
}
return UNULL;
}
/**
* Locate ACPI root system description table
*
* @v ebda Extended BIOS data area, or UNULL
* @ret rsdt ACPI root system description table, or UNULL
*/
userptr_t acpi_find_rsdt ( userptr_t ebda ) {
userptr_t rsdt;
/* Search EBDA, if applicable */
if ( ebda ) {
rsdt = acpi_find_rsdt_range ( ebda, RSDP_EBDA_LEN );
if ( rsdt )
return rsdt;
}
/* Search fixed BIOS area */
rsdt = acpi_find_rsdt_range ( phys_to_user ( RSDP_BIOS_START ),
RSDP_BIOS_LEN );
if ( rsdt )
return rsdt;
return UNULL;
}
/**
* Locate ACPI table
*
* @v rsdt ACPI root system description table
* @v signature Requested table signature
* @v index Requested index of table with this signature
* @ret table Table, or UNULL if not found
*/
userptr_t acpi_find ( userptr_t rsdt, uint32_t signature, unsigned int index ) {
struct acpi_description_header acpi;
struct acpi_rsdt *rsdtab;
typeof ( rsdtab->entry[0] ) entry;
userptr_t table;
size_t len;
unsigned int count;
unsigned int i;
/* Read RSDT header */
copy_from_user ( &acpi, rsdt, 0, sizeof ( acpi ) );
if ( acpi.signature != cpu_to_le32 ( RSDT_SIGNATURE ) ) {
DBGC ( rsdt, "RSDT %#08lx has invalid signature:\n",
user_to_phys ( rsdt, 0 ) );
DBGC_HDA ( rsdt, user_to_phys ( rsdt, 0 ), &acpi,
sizeof ( acpi ) );
return UNULL;
}
len = le32_to_cpu ( acpi.length );
if ( len < sizeof ( rsdtab->acpi ) ) {
DBGC ( rsdt, "RSDT %#08lx has invalid length:\n",
user_to_phys ( rsdt, 0 ) );
DBGC_HDA ( rsdt, user_to_phys ( rsdt, 0 ), &acpi,
sizeof ( acpi ) );
return UNULL;
}
/* Calculate number of entries */
count = ( ( len - sizeof ( rsdtab->acpi ) ) / sizeof ( entry ) );
/* Search through entries */
for ( i = 0 ; i < count ; i++ ) {
/* Get table address */
copy_from_user ( &entry, rsdt,
offsetof ( typeof ( *rsdtab ), entry[i] ),
sizeof ( entry ) );
/* Read table header */
table = phys_to_user ( entry );
copy_from_user ( &acpi.signature, table, 0,
sizeof ( acpi.signature ) );
/* Check table signature */
if ( acpi.signature != cpu_to_le32 ( signature ) )
continue;
/* Check index */
if ( index-- )
continue;
DBGC ( rsdt, "RSDT %#08lx found %s at %08lx\n",
user_to_phys ( rsdt, 0 ), acpi_name ( signature ),
user_to_phys ( table, 0 ) );
return table;
}
DBGC ( rsdt, "RSDT %#08lx could not find %s\n",
user_to_phys ( rsdt, 0 ), acpi_name ( signature ) );
return UNULL;
}
/**
* Extract \_Sx value from DSDT/SSDT
*
* @v zsdt DSDT or SSDT
* @v signature Signature (e.g. "_S5_")
* @ret sx \_Sx value, or negative error
*
* In theory, extracting the \_Sx value from the DSDT/SSDT requires a
* full ACPI parser plus some heuristics to work around the various
* broken encodings encountered in real ACPI implementations.
*
* In practice, we can get the same result by scanning through the
* DSDT/SSDT for the signature (e.g. "_S5_"), extracting the first
* four bytes, removing any bytes with bit 3 set, and treating
* whatever is left as a little-endian value. This is one of the
* uglier hacks I have ever implemented, but it's still prettier than
* the ACPI specification itself.
*/
static int acpi_sx_zsdt ( userptr_t zsdt, uint32_t signature ) {
struct acpi_description_header acpi;
union {
uint32_t dword;
uint8_t byte[4];
} buf;
size_t offset;
size_t len;
unsigned int sx;
uint8_t *byte;
/* Read table header */
copy_from_user ( &acpi, zsdt, 0, sizeof ( acpi ) );
len = le32_to_cpu ( acpi.length );
/* Locate signature */
for ( offset = sizeof ( acpi ) ;
( ( offset + sizeof ( buf ) /* signature */ + 3 /* pkg header */
+ sizeof ( buf ) /* value */ ) < len ) ;
offset++ ) {
/* Check signature */
copy_from_user ( &buf, zsdt, offset, sizeof ( buf ) );
if ( buf.dword != cpu_to_le32 ( signature ) )
continue;
DBGC ( zsdt, "DSDT/SSDT %#08lx found %s at offset %#zx\n",
user_to_phys ( zsdt, 0 ), acpi_name ( signature ),
offset );
offset += sizeof ( buf );
/* Read first four bytes of value */
copy_from_user ( &buf, zsdt, ( offset + 3 /* pkg header */ ),
sizeof ( buf ) );
DBGC ( zsdt, "DSDT/SSDT %#08lx found %s containing "
"%02x:%02x:%02x:%02x\n", user_to_phys ( zsdt, 0 ),
acpi_name ( signature ), buf.byte[0], buf.byte[1],
buf.byte[2], buf.byte[3] );
/* Extract \Sx value. There are three potential
* encodings that we might encounter:
*
* - SLP_TYPa, SLP_TYPb, rsvd, rsvd
*
* - <byteprefix>, SLP_TYPa, <byteprefix>, SLP_TYPb, ...
*
* - <dwordprefix>, SLP_TYPa, SLP_TYPb, 0, 0
*
* Since <byteprefix> and <dwordprefix> both have bit
* 3 set, and valid SLP_TYPx must have bit 3 clear
* (since SLP_TYPx is a 3-bit field), we can just skip
* any bytes with bit 3 set.
*/
byte = &buf.byte[0];
if ( *byte & 0x08 )
byte++;
sx = *(byte++);
if ( *byte & 0x08 )
byte++;
sx |= ( *byte << 8 );
return sx;
}
return -ENOENT;
}
/**
* Extract \_Sx value from DSDT/SSDT
*
* @v rsdt ACPI root system description table
* @v signature Signature (e.g. "_S5_")
* @ret sx \_Sx value, or negative error
*/
int acpi_sx ( userptr_t rsdt, uint32_t signature ) {
struct acpi_fadt fadtab;
userptr_t fadt;
userptr_t dsdt;
userptr_t ssdt;
unsigned int i;
int sx;
/* Try DSDT first */
fadt = acpi_find ( rsdt, FADT_SIGNATURE, 0 );
if ( fadt ) {
copy_from_user ( &fadtab, fadt, 0, sizeof ( fadtab ) );
dsdt = phys_to_user ( fadtab.dsdt );
if ( ( sx = acpi_sx_zsdt ( dsdt, signature ) ) >= 0 )
return sx;
}
/* Try all SSDTs */
for ( i = 0 ; ; i++ ) {
ssdt = acpi_find ( rsdt, SSDT_SIGNATURE, i );
if ( ! ssdt )
break;
if ( ( sx = acpi_sx_zsdt ( ssdt, signature ) ) >= 0 )
return sx;
}
DBGC ( rsdt, "RSDT %#08lx could not find \\_Sx \"%s\"\n",
user_to_phys ( rsdt, 0 ), acpi_name ( signature ) );
return -ENOENT;
}
/******************************************************************************
*
* Interface methods

View File

@ -11,6 +11,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#include <stdint.h>
#include <ipxe/interface.h>
#include <ipxe/uaccess.h>
/**
* An ACPI description header
@ -51,6 +52,76 @@ struct acpi_description_header {
#define ACPI_SIGNATURE( a, b, c, d ) \
( ( (a) << 0 ) | ( (b) << 8 ) | ( (c) << 16 ) | ( (d) << 24 ) )
/** Root System Description Pointer signature */
#define RSDP_SIGNATURE { 'R', 'S', 'D', ' ', 'P', 'T', 'R', ' ' }
/** Root System Description Pointer */
struct acpi_rsdp {
/** Signature */
char signature[8];
/** To make sum of entire table == 0 */
uint8_t checksum;
/** OEM identification */
char oem_id[6];
/** Revision */
uint8_t revision;
/** Physical address of RSDT */
uint32_t rsdt;
} __attribute__ (( packed ));
/** EBDA RSDP length */
#define RSDP_EBDA_LEN 0x400
/** Fixed BIOS area RSDP start address */
#define RSDP_BIOS_START 0xe0000
/** Fixed BIOS area RSDP length */
#define RSDP_BIOS_LEN 0x20000
/** Stride at which to search for RSDP */
#define RSDP_STRIDE 16
/** Root System Description Table (RSDT) signature */
#define RSDT_SIGNATURE ACPI_SIGNATURE ( 'R', 'S', 'D', 'T' )
/** ACPI Root System Description Table (RSDT) */
struct acpi_rsdt {
/** ACPI header */
struct acpi_description_header acpi;
/** ACPI table entries */
uint32_t entry[0];
} __attribute__ (( packed ));
/** Fixed ACPI Description Table (FADT) signature */
#define FADT_SIGNATURE ACPI_SIGNATURE ( 'F', 'A', 'C', 'P' )
/** Fixed ACPI Description Table (FADT) */
struct acpi_fadt {
/** ACPI header */
struct acpi_description_header acpi;
/** Physical address of FACS */
uint32_t facs;
/** Physical address of DSDT */
uint32_t dsdt;
/** Unused by iPXE */
uint8_t unused[20];
/** PM1a Control Register Block */
uint32_t pm1a_cnt_blk;
/** PM1b Control Register Block */
uint32_t pm1b_cnt_blk;
} __attribute__ (( packed ));
/** ACPI PM1 Control Register (within PM1a_CNT_BLK or PM1A_CNT_BLK) */
#define ACPI_PM1_CNT 0
#define ACPI_PM1_CNT_SLP_TYP(x) ( (x) << 10 ) /**< Sleep type */
#define ACPI_PM1_CNT_SLP_EN ( 1 << 13 ) /**< Sleep enable */
/** Differentiated System Description Table (DSDT) signature */
#define DSDT_SIGNATURE ACPI_SIGNATURE ( 'D', 'S', 'D', 'T' )
/** Secondary System Description Table (SSDT) signature */
#define SSDT_SIGNATURE ACPI_SIGNATURE ( 'S', 'S', 'D', 'T' )
extern int acpi_describe ( struct interface *interface,
struct acpi_description_header *acpi, size_t len );
#define acpi_describe_TYPE( object_type ) \
@ -59,5 +130,9 @@ extern int acpi_describe ( struct interface *interface,
size_t len ) )
extern void acpi_fix_checksum ( struct acpi_description_header *acpi );
extern userptr_t acpi_find_rsdt ( userptr_t ebda );
extern userptr_t acpi_find ( userptr_t rsdt, uint32_t signature,
unsigned int index );
extern int acpi_sx ( userptr_t rsdt, uint32_t signature );
#endif /* _IPXE_ACPI_H */