summaryrefslogtreecommitdiffstats
path: root/src/core/acpi.c
diff options
context:
space:
mode:
authorMichael Brown2016-07-10 20:25:26 +0200
committerMichael Brown2016-07-11 15:05:18 +0200
commite19c0a8fd2bc8c2331c5fabe17ea56e7c35d1900 (patch)
tree0aba5ee1c1274ff613d786c18b0d004cce7b9dc4 /src/core/acpi.c
parent[rng] Check for functioning RTC interrupt (diff)
downloadipxe-e19c0a8fd2bc8c2331c5fabe17ea56e7c35d1900.tar.gz
ipxe-e19c0a8fd2bc8c2331c5fabe17ea56e7c35d1900.tar.xz
ipxe-e19c0a8fd2bc8c2331c5fabe17ea56e7c35d1900.zip
[acpi] Add support for ACPI power off
Signed-off-by: Michael Brown <mcb30@ipxe.org>
Diffstat (limited to 'src/core/acpi.c')
-rw-r--r--src/core/acpi.c274
1 files changed, 274 insertions, 0 deletions
diff --git a/src/core/acpi.c b/src/core/acpi.c
index b0ccfa78..955637e0 100644
--- a/src/core/acpi.c
+++ b/src/core/acpi.c
@@ -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>
@@ -41,6 +43,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
*
* @v acpi ACPI table header
@@ -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