diff options
author | Michael Brown | 2016-07-10 20:25:26 +0200 |
---|---|---|
committer | Michael Brown | 2016-07-11 15:05:18 +0200 |
commit | e19c0a8fd2bc8c2331c5fabe17ea56e7c35d1900 (patch) | |
tree | 0aba5ee1c1274ff613d786c18b0d004cce7b9dc4 /src/core | |
parent | [rng] Check for functioning RTC interrupt (diff) | |
download | ipxe-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')
-rw-r--r-- | src/core/acpi.c | 274 |
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 |