diff options
Diffstat (limited to 'src/arch/x86/interface/pcbios/acpipwr.c')
-rw-r--r-- | src/arch/x86/interface/pcbios/acpipwr.c | 70 |
1 files changed, 66 insertions, 4 deletions
diff --git a/src/arch/x86/interface/pcbios/acpipwr.c b/src/arch/x86/interface/pcbios/acpipwr.c index dc164c7..3dac6b6 100644 --- a/src/arch/x86/interface/pcbios/acpipwr.c +++ b/src/arch/x86/interface/pcbios/acpipwr.c @@ -43,6 +43,69 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define S5_SIGNATURE ACPI_SIGNATURE ( '_', 'S', '5', '_' ) /** + * Extract \_Sx value from DSDT/SSDT + * + * @v zsdt DSDT or SSDT + * @v len Length of DSDT/SSDT + * @v offset Offset of signature within DSDT/SSDT + * @v data Data buffer + * @ret rc Return status code + * + * 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_extract_sx ( userptr_t zsdt, size_t len, size_t offset, + void *data ) { + unsigned int *sx = data; + uint8_t bytes[4]; + uint8_t *byte; + + /* Skip signature and package header */ + offset += ( 4 /* signature */ + 3 /* package header */ ); + + /* Sanity check */ + if ( ( offset + sizeof ( bytes ) /* value */ ) > len ) { + return -EINVAL; + } + + /* Read first four bytes of value */ + copy_from_user ( bytes, zsdt, offset, sizeof ( bytes ) ); + DBGC ( colour, "ACPI found \\_Sx containing %02x:%02x:%02x:%02x\n", + bytes[0], bytes[1], bytes[2], bytes[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 = bytes; + if ( *byte & 0x08 ) + byte++; + *sx = *(byte++); + if ( *byte & 0x08 ) + byte++; + *sx |= ( *byte << 8 ); + + return 0; +} + +/** * Power off the computer using ACPI * * @ret rc Return status code @@ -56,7 +119,7 @@ int acpi_poweroff ( void ) { unsigned int pm1b_cnt; unsigned int slp_typa; unsigned int slp_typb; - int s5; + unsigned int s5; int rc; /* Locate FADT */ @@ -74,9 +137,8 @@ int acpi_poweroff ( void ) { pm1b_cnt = ( pm1b_cnt_blk + ACPI_PM1_CNT ); /* Extract \_S5 from DSDT or any SSDT */ - s5 = acpi_sx ( S5_SIGNATURE ); - if ( s5 < 0 ) { - rc = s5; + if ( ( rc = acpi_extract ( S5_SIGNATURE, &s5, + acpi_extract_sx ) ) != 0 ) { DBGC ( colour, "ACPI could not extract \\_S5: %s\n", strerror ( rc ) ); return rc; |