From: Michael Brown Date: Sun, 10 Jul 2016 18:25:26 +0000 (+0100) Subject: [acpi] Add support for ACPI power off X-Git-Tag: v1.20.1~396 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e19c0a8fd2bc8c2331c5fabe17ea56e7c35d1900;p=thirdparty%2Fipxe.git [acpi] Add support for ACPI power off Signed-off-by: Michael Brown --- diff --git a/src/arch/x86/include/bios.h b/src/arch/x86/include/bios.h index 988bbc62b..a5a5d887c 100644 --- a/src/arch/x86/include/bios.h +++ b/src/arch/x86/include/bios.h @@ -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 diff --git a/src/arch/x86/include/bits/errfile.h b/src/arch/x86/include/bits/errfile.h index 79b6f882e..f4816e62a 100644 --- a/src/arch/x86/include/bits/errfile.h +++ b/src/arch/x86/include/bits/errfile.h @@ -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 ) diff --git a/src/arch/x86/include/ipxe/acpipwr.h b/src/arch/x86/include/ipxe/acpipwr.h new file mode 100644 index 000000000..93da09429 --- /dev/null +++ b/src/arch/x86/include/ipxe/acpipwr.h @@ -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 */ diff --git a/src/arch/x86/include/ipxe/apm.h b/src/arch/x86/include/ipxe/apm.h new file mode 100644 index 000000000..21d913ac4 --- /dev/null +++ b/src/arch/x86/include/ipxe/apm.h @@ -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 */ diff --git a/src/arch/x86/interface/pcbios/acpipwr.c b/src/arch/x86/interface/pcbios/acpipwr.c new file mode 100644 index 000000000..63b986b68 --- /dev/null +++ b/src/arch/x86/interface/pcbios/acpipwr.c @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2016 Michael Brown . + * + * 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 +#include +#include +#include +#include +#include +#include + +/** @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; +} diff --git a/src/arch/x86/interface/pcbios/apm.c b/src/arch/x86/interface/pcbios/apm.c index 50b19cb81..680dbb16a 100644 --- a/src/arch/x86/interface/pcbios/apm.c +++ b/src/arch/x86/interface/pcbios/apm.c @@ -32,14 +32,14 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include -#include +#include /** * 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 ); diff --git a/src/arch/x86/interface/pcbios/bios_reboot.c b/src/arch/x86/interface/pcbios/bios_reboot.c index ed18dde0b..c6c5a5a91 100644 --- a/src/arch/x86/interface/pcbios/bios_reboot.c +++ b/src/arch/x86/interface/pcbios/bios_reboot.c @@ -32,6 +32,8 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include #include +#include +#include /** * 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 ); diff --git a/src/core/acpi.c b/src/core/acpi.c index b0ccfa78d..955637e00 100644 --- a/src/core/acpi.c +++ b/src/core/acpi.c @@ -24,6 +24,8 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include +#include +#include #include #include @@ -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 + * + * - , SLP_TYPa, , SLP_TYPb, ... + * + * - , SLP_TYPa, SLP_TYPb, 0, 0 + * + * Since and 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 diff --git a/src/include/ipxe/acpi.h b/src/include/ipxe/acpi.h index 2ccd691ed..17d29b9df 100644 --- a/src/include/ipxe/acpi.h +++ b/src/include/ipxe/acpi.h @@ -11,6 +11,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include +#include /** * 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 */