--- /dev/null
+/*
+ * 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;
+}
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>
******************************************************************************
*/
+/**
+ * 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
*
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
#include <stdint.h>
#include <ipxe/interface.h>
+#include <ipxe/uaccess.h>
/**
* An 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 ) \
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 */