aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Brown <mcb30@ipxe.org>2021-09-08 12:53:12 +0100
committerMichael Brown <mcb30@ipxe.org>2021-09-08 14:46:30 +0100
commit02ec659b73b0998a275e79ec06a5b7d674dfad07 (patch)
tree8bcc4f8f36543cc2f3fbe88e999155c8cbc07617
parente09e1142a3bd8bdb702efc92994c419a53e9933b (diff)
downloadipxe-02ec659b73b0998a275e79ec06a5b7d674dfad07.zip
ipxe-02ec659b73b0998a275e79ec06a5b7d674dfad07.tar.gz
ipxe-02ec659b73b0998a275e79ec06a5b7d674dfad07.tar.bz2
[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.c70
-rw-r--r--src/core/acpi.c94
-rw-r--r--src/include/ipxe/acpi.h4
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 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;
diff --git a/src/core/acpi.c b/src/core/acpi.c
index 52eb63a..aa486da 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 81ef7ff..7df3ec2 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 ) );