diff options
author | Michael Brown <mcb30@ipxe.org> | 2021-09-08 12:53:12 +0100 |
---|---|---|
committer | Michael Brown <mcb30@ipxe.org> | 2021-09-08 14:46:30 +0100 |
commit | 02ec659b73b0998a275e79ec06a5b7d674dfad07 (patch) | |
tree | 8bcc4f8f36543cc2f3fbe88e999155c8cbc07617 | |
parent | e09e1142a3bd8bdb702efc92994c419a53e9933b (diff) | |
download | ipxe-02ec659b73b0998a275e79ec06a5b7d674dfad07.tar.gz |
[acpi] Generalise DSDT/SSDT data extraction logic
Allow for the DSDT/SSDT signature-scanning and value extraction code
to be reused for extracting a pass-through MAC address.
Signed-off-by: Michael Brown <mcb30@ipxe.org>
-rw-r--r-- | src/arch/x86/interface/pcbios/acpipwr.c | 70 | ||||
-rw-r--r-- | src/core/acpi.c | 94 | ||||
-rw-r--r-- | src/include/ipxe/acpi.h | 4 |
3 files changed, 99 insertions, 69 deletions
diff --git a/src/arch/x86/interface/pcbios/acpipwr.c b/src/arch/x86/interface/pcbios/acpipwr.c index dc164c7d5..3dac6b605 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; diff --git a/src/core/acpi.c b/src/core/acpi.c index 52eb63a04..aa486da93 100644 --- a/src/core/acpi.c +++ b/src/core/acpi.c @@ -169,33 +169,22 @@ userptr_t acpi_find_via_rsdt ( uint32_t signature, unsigned int index ) { } /** - * Extract \_Sx value from DSDT/SSDT + * Extract 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. + * @v data Data buffer + * @v extract Extraction method + * @ret rc Return status code */ -static int acpi_sx_zsdt ( userptr_t zsdt, uint32_t signature ) { +static int acpi_zsdt ( userptr_t zsdt, uint32_t signature, void *data, + int ( * extract ) ( userptr_t zsdt, size_t len, + size_t offset, void *data ) ) { struct acpi_header acpi; - union { - uint32_t dword; - uint8_t byte[4]; - } buf; + uint32_t buf; size_t offset; size_t len; - unsigned int sx; - uint8_t *byte; + int rc; /* Read table header */ copy_from_user ( &acpi, zsdt, 0, sizeof ( acpi ) ); @@ -203,75 +192,51 @@ static int acpi_sx_zsdt ( userptr_t zsdt, uint32_t signature ) { /* Locate signature */ for ( offset = sizeof ( acpi ) ; - ( ( offset + sizeof ( buf ) /* signature */ + 3 /* pkg header */ - + sizeof ( buf ) /* value */ ) < len ) ; + ( ( offset + sizeof ( buf ) /* signature */ ) < len ) ; offset++ ) { /* Check signature */ copy_from_user ( &buf, zsdt, offset, sizeof ( buf ) ); - if ( buf.dword != cpu_to_le32 ( signature ) ) + if ( buf != 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; + + /* Attempt to extract data */ + if ( ( rc = extract ( zsdt, len, offset, data ) ) == 0 ) + return 0; } return -ENOENT; } /** - * Extract \_Sx value from DSDT/SSDT + * Extract value from DSDT/SSDT * * @v signature Signature (e.g. "_S5_") - * @ret sx \_Sx value, or negative error + * @v data Data buffer + * @v extract Extraction method + * @ret rc Return status code */ -int acpi_sx ( uint32_t signature ) { +int acpi_extract ( uint32_t signature, void *data, + int ( * extract ) ( userptr_t zsdt, size_t len, + size_t offset, void *data ) ) { struct acpi_fadt fadtab; userptr_t fadt; userptr_t dsdt; userptr_t ssdt; unsigned int i; - int sx; + int rc; /* Try DSDT first */ fadt = acpi_find ( 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; + if ( ( rc = acpi_zsdt ( dsdt, signature, data, + extract ) ) == 0 ) + return 0; } /* Try all SSDTs */ @@ -279,11 +244,12 @@ int acpi_sx ( uint32_t signature ) { ssdt = acpi_find ( SSDT_SIGNATURE, i ); if ( ! ssdt ) break; - if ( ( sx = acpi_sx_zsdt ( ssdt, signature ) ) >= 0 ) - return sx; + if ( ( rc = acpi_zsdt ( ssdt, signature, data, + extract ) ) == 0 ) + return 0; } - DBGC ( colour, "ACPI could not find \\_Sx \"%s\"\n", + DBGC ( colour, "ACPI could not find \"%s\"\n", acpi_name ( signature ) ); return -ENOENT; } diff --git a/src/include/ipxe/acpi.h b/src/include/ipxe/acpi.h index 81ef7ff76..7df3ec21c 100644 --- a/src/include/ipxe/acpi.h +++ b/src/include/ipxe/acpi.h @@ -387,7 +387,9 @@ acpi_describe ( struct interface *interface ); typeof ( struct acpi_descriptor * ( object_type ) ) extern void acpi_fix_checksum ( struct acpi_header *acpi ); -extern int acpi_sx ( uint32_t signature ); +extern int acpi_extract ( uint32_t signature, void *data, + int ( * extract ) ( userptr_t zsdt, size_t len, + size_t offset, void *data ) ); extern void acpi_add ( struct acpi_descriptor *desc ); extern void acpi_del ( struct acpi_descriptor *desc ); extern int acpi_install ( int ( * install ) ( struct acpi_header *acpi ) ); |