From e09e1142a3bd8bdb702efc92994c419a53e9933b Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Fri, 23 Jul 2021 11:32:04 +0100 Subject: [efi] Record cached ProxyDHCPOFFER and PXEBSACK, if present Commit cd3de55 ("[efi] Record cached DHCPACK from loaded image's device handle, if present") added the ability for a chainloaded UEFI iPXE to reuse an IPv4 address and DHCP options previously obtained by a built-in PXE stack, without needing to perform a second DHCP request. Extend this to also record the cached ProxyDHCPOFFER and PXEBSACK obtained from the EFI_PXE_BASE_CODE_PROTOCOL instance installed on the loaded image's device handle, if present. This allows a chainloaded UEFI iPXE to reuse a boot filename or other options that were provided via a ProxyDHCP or PXE boot server mechanism, rather than by standard DHCP. Tested-by: Andreas Hammarskjöld Signed-off-by: Michael Brown --- src/include/ipxe/cachedhcp.h | 9 ++++++++- src/include/ipxe/dhcppkt.h | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) (limited to 'src/include') diff --git a/src/include/ipxe/cachedhcp.h b/src/include/ipxe/cachedhcp.h index 7765c6455..39ce74543 100644 --- a/src/include/ipxe/cachedhcp.h +++ b/src/include/ipxe/cachedhcp.h @@ -12,6 +12,13 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include -extern int cachedhcp_record ( userptr_t data, size_t max_len ); +struct cached_dhcp_packet; + +extern struct cached_dhcp_packet cached_dhcpack; +extern struct cached_dhcp_packet cached_proxydhcp; +extern struct cached_dhcp_packet cached_pxebs; + +extern int cachedhcp_record ( struct cached_dhcp_packet *cache, userptr_t data, + size_t max_len ); #endif /* _IPXE_CACHEDHCP_H */ diff --git a/src/include/ipxe/dhcppkt.h b/src/include/ipxe/dhcppkt.h index f13dfc93d..86075960a 100644 --- a/src/include/ipxe/dhcppkt.h +++ b/src/include/ipxe/dhcppkt.h @@ -56,7 +56,7 @@ dhcppkt_put ( struct dhcp_packet *dhcppkt ) { * @v dhcppkt DHCP packet * @ret len Used length */ -static inline int dhcppkt_len ( struct dhcp_packet *dhcppkt ) { +static inline size_t dhcppkt_len ( struct dhcp_packet *dhcppkt ) { return ( offsetof ( struct dhcphdr, options ) + dhcppkt->options.used_len ); } -- cgit v1.2.3-55-g7522 From 02ec659b73b0998a275e79ec06a5b7d674dfad07 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Wed, 8 Sep 2021 12:53:12 +0100 Subject: [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 --- src/arch/x86/interface/pcbios/acpipwr.c | 70 ++++++++++++++++++++++-- src/core/acpi.c | 94 +++++++++++---------------------- src/include/ipxe/acpi.h | 4 +- 3 files changed, 99 insertions(+), 69 deletions(-) (limited to 'src/include') 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 @@ -42,6 +42,69 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); /** _S5_ signature */ #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 + * + * - , SLP_TYPa, , SLP_TYPb, ... + * + * - , SLP_TYPa, SLP_TYPb, 0, 0 + * + * Since and 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 * @@ -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 - * - * - , SLP_TYPa, , SLP_TYPb, ... - * - * - , SLP_TYPa, SLP_TYPb, 0, 0 - * - * Since and 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 ) ); -- cgit v1.2.3-55-g7522 From 0cc4c42f0af716bc5a363ad699160926e91a4d35 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Wed, 8 Sep 2021 13:50:20 +0100 Subject: [acpi] Allow for extraction of a MAC address from the DSDT/SSDT Some vendors provide a "system MAC address" within the DSDT/SSDT, to be used to override the MAC address for a USB docking station. A full implementation would require an ACPI bytecode interpreter, since at least one OEM allows the MAC address to be constructed by executable ACPI bytecode (rather than a fixed data structure). We instead attempt to extract a plausible-looking "_AUXMAC_#.....#" string that appears shortly after an "AMAC" or "MACA" signature. This should work for most implementations encountered in practice. Debugged-by: Andreas Hammarskjöld Signed-off-by: Michael Brown --- src/core/acpimac.c | 154 +++++++++++++++++++++++++++++++++++++++++++++ src/include/ipxe/acpimac.h | 14 +++++ src/include/ipxe/errfile.h | 1 + 3 files changed, 169 insertions(+) create mode 100644 src/core/acpimac.c create mode 100644 src/include/ipxe/acpimac.h (limited to 'src/include') diff --git a/src/core/acpimac.c b/src/core/acpimac.c new file mode 100644 index 000000000..1cc8220b1 --- /dev/null +++ b/src/core/acpimac.c @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2021 Michael Brown . + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include +#include +#include +#include +#include +#include +#include + +/** @file + * + * ACPI MAC address + * + */ + +/** Colour for debug messages */ +#define colour FADT_SIGNATURE + +/** AMAC signature */ +#define AMAC_SIGNATURE ACPI_SIGNATURE ( 'A', 'M', 'A', 'C' ) + +/** MACA signature */ +#define MACA_SIGNATURE ACPI_SIGNATURE ( 'M', 'A', 'C', 'A' ) + +/** Maximum number of bytes to skip after AMAC/MACA signature + * + * This is entirely empirical. + */ +#define AUXMAC_MAX_SKIP 8 + +/** + * Extract MAC address 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 + * + * Some vendors provide a "system MAC address" within the DSDT/SSDT, + * to be used to override the MAC address for a USB docking station. + * + * A full implementation would require an ACPI bytecode interpreter, + * since at least one OEM allows the MAC address to be constructed by + * executable ACPI bytecode (rather than a fixed data structure). + * + * We instead attempt to extract a plausible-looking "_AUXMAC_#.....#" + * string that appears shortly after an "AMAC" or "MACA" signature. + * This should work for most implementations encountered in practice. + */ +static int acpi_extract_mac ( userptr_t zsdt, size_t len, size_t offset, + void *data ) { + static const char prefix[9] = "_AUXMAC_#"; + uint8_t *hw_addr = data; + size_t skip = 0; + char auxmac[ sizeof ( prefix ) /* "_AUXMAC_#" */ + + ( ETH_ALEN * 2 ) /* MAC */ + 1 /* "#" */ + 1 /* NUL */ ]; + char *mac = &auxmac[ sizeof ( prefix ) ]; + int decoded_len; + int rc; + + /* Skip signature and at least one tag byte */ + offset += ( 4 /* signature */ + 1 /* tag byte */ ); + + /* Scan for "_AUXMAC_#.....#" close to signature */ + for ( skip = 0 ; + ( ( skip < AUXMAC_MAX_SKIP ) && + ( offset + skip + sizeof ( auxmac ) ) < len ) ; + skip++ ) { + + /* Read value */ + copy_from_user ( auxmac, zsdt, ( offset + skip ), + sizeof ( auxmac ) ); + + /* Check for expected format */ + if ( memcmp ( auxmac, prefix, sizeof ( prefix ) ) != 0 ) + continue; + if ( auxmac[ sizeof ( auxmac ) - 2 ] != '#' ) + continue; + if ( auxmac[ sizeof ( auxmac ) - 1 ] != '\0' ) + continue; + DBGC ( colour, "ACPI found MAC string \"%s\"\n", auxmac ); + + /* Terminate MAC address string */ + mac = &auxmac[ sizeof ( prefix ) ]; + mac[ ETH_ALEN * 2 ] = '\0'; + + /* Decode MAC address */ + decoded_len = base16_decode ( mac, hw_addr, ETH_ALEN ); + if ( decoded_len < 0 ) { + rc = decoded_len; + DBGC ( colour, "ACPI could not decode MAC \"%s\": %s\n", + mac, strerror ( rc ) ); + return rc; + } + + /* Check MAC address validity */ + if ( ! is_valid_ether_addr ( hw_addr ) ) { + DBGC ( colour, "ACPI has invalid MAC %s\n", + eth_ntoa ( hw_addr ) ); + return -EINVAL; + } + + return 0; + } + + return -ENOENT; +} + +/** + * Extract MAC address from DSDT/SSDT + * + * @v hw_addr MAC address to fill in + * @ret rc Return status code + */ +int acpi_mac ( uint8_t *hw_addr ) { + int rc; + + /* Look for an "AMAC" address */ + if ( ( rc = acpi_extract ( AMAC_SIGNATURE, hw_addr, + acpi_extract_mac ) ) == 0 ) + return 0; + + /* Look for a "MACA" address */ + if ( ( rc = acpi_extract ( MACA_SIGNATURE, hw_addr, + acpi_extract_mac ) ) == 0 ) + return 0; + + return -ENOENT; +} diff --git a/src/include/ipxe/acpimac.h b/src/include/ipxe/acpimac.h new file mode 100644 index 000000000..de673eb28 --- /dev/null +++ b/src/include/ipxe/acpimac.h @@ -0,0 +1,14 @@ +#ifndef _IPXE_ACPIMAC_H +#define _IPXE_ACPIMAC_H + +/** @file + * + * ACPI MAC address + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +extern int acpi_mac ( uint8_t *hw_addr ); + +#endif /* _IPXE_ACPIMAC_H */ diff --git a/src/include/ipxe/errfile.h b/src/include/ipxe/errfile.h index cf5757874..23e406b62 100644 --- a/src/include/ipxe/errfile.h +++ b/src/include/ipxe/errfile.h @@ -77,6 +77,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define ERRFILE_fdt ( ERRFILE_CORE | 0x00250000 ) #define ERRFILE_dma ( ERRFILE_CORE | 0x00260000 ) #define ERRFILE_cachedhcp ( ERRFILE_CORE | 0x00270000 ) +#define ERRFILE_acpimac ( ERRFILE_CORE | 0x00280000 ) #define ERRFILE_eisa ( ERRFILE_DRIVER | 0x00000000 ) #define ERRFILE_isa ( ERRFILE_DRIVER | 0x00010000 ) -- cgit v1.2.3-55-g7522