diff options
Diffstat (limited to 'src/core')
| -rw-r--r-- | src/core/acpi.c | 94 | ||||
| -rw-r--r-- | src/core/acpimac.c | 154 | ||||
| -rw-r--r-- | src/core/cachedhcp.c | 175 |
3 files changed, 314 insertions, 109 deletions
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/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 <mbrown@fensystems.co.uk>. + * + * 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 <string.h> +#include <errno.h> +#include <ipxe/acpi.h> +#include <ipxe/base16.h> +#include <ipxe/ethernet.h> +#include <ipxe/if_ether.h> +#include <ipxe/acpimac.h> + +/** @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/core/cachedhcp.c b/src/core/cachedhcp.c index 0e7da4bf2..2fa9b0c73 100644 --- a/src/core/cachedhcp.c +++ b/src/core/cachedhcp.c @@ -37,29 +37,121 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); * */ +/** A cached DHCP packet */ +struct cached_dhcp_packet { + /** Settings block name */ + const char *name; + /** DHCP packet (if any) */ + struct dhcp_packet *dhcppkt; +}; + /** Cached DHCPACK */ -static struct dhcp_packet *cached_dhcpack; +struct cached_dhcp_packet cached_dhcpack = { + .name = DHCP_SETTINGS_NAME, +}; + +/** Cached ProxyDHCPOFFER */ +struct cached_dhcp_packet cached_proxydhcp = { + .name = PROXYDHCP_SETTINGS_NAME, +}; + +/** Cached PXEBSACK */ +struct cached_dhcp_packet cached_pxebs = { + .name = PXEBS_SETTINGS_NAME, +}; + +/** List of cached DHCP packets */ +static struct cached_dhcp_packet *cached_packets[] = { + &cached_dhcpack, + &cached_proxydhcp, + &cached_pxebs, +}; /** Colour for debug messages */ #define colour &cached_dhcpack /** - * Record cached DHCPACK + * Free cached DHCP packet * + * @v cache Cached DHCP packet + */ +static void cachedhcp_free ( struct cached_dhcp_packet *cache ) { + + dhcppkt_put ( cache->dhcppkt ); + cache->dhcppkt = NULL; +} + +/** + * Apply cached DHCP packet settings + * + * @v cache Cached DHCP packet + * @v netdev Network device, or NULL + * @ret rc Return status code + */ +static int cachedhcp_apply ( struct cached_dhcp_packet *cache, + struct net_device *netdev ) { + struct settings *settings; + int rc; + + /* Do nothing if cache is empty */ + if ( ! cache->dhcppkt ) + return 0; + + /* Do nothing unless cached packet's MAC address matches this + * network device, if specified. + */ + if ( netdev ) { + if ( memcmp ( netdev->ll_addr, cache->dhcppkt->dhcphdr->chaddr, + netdev->ll_protocol->ll_addr_len ) != 0 ) { + DBGC ( colour, "CACHEDHCP %s does not match %s\n", + cache->name, netdev->name ); + return 0; + } + DBGC ( colour, "CACHEDHCP %s is for %s\n", + cache->name, netdev->name ); + } + + /* Select appropriate parent settings block */ + settings = ( netdev ? netdev_settings ( netdev ) : NULL ); + + /* Register settings */ + if ( ( rc = register_settings ( &cache->dhcppkt->settings, settings, + cache->name ) ) != 0 ) { + DBGC ( colour, "CACHEDHCP %s could not register settings: %s\n", + cache->name, strerror ( rc ) ); + return rc; + } + + /* Free cached DHCP packet */ + cachedhcp_free ( cache ); + + return 0; +} + +/** + * Record cached DHCP packet + * + * @v cache Cached DHCP packet * @v data DHCPACK packet buffer * @v max_len Maximum possible length * @ret rc Return status code */ -int cachedhcp_record ( userptr_t data, size_t max_len ) { +int cachedhcp_record ( struct cached_dhcp_packet *cache, userptr_t data, + size_t max_len ) { struct dhcp_packet *dhcppkt; struct dhcp_packet *tmp; struct dhcphdr *dhcphdr; + unsigned int i; size_t len; + /* Free any existing cached packet */ + cachedhcp_free ( cache ); + /* Allocate and populate DHCP packet */ dhcppkt = zalloc ( sizeof ( *dhcppkt ) + max_len ); if ( ! dhcppkt ) { - DBGC ( colour, "CACHEDHCP could not allocate copy\n" ); + DBGC ( colour, "CACHEDHCP %s could not allocate copy\n", + cache->name ); return -ENOMEM; } dhcphdr = ( ( ( void * ) dhcppkt ) + sizeof ( *dhcppkt ) ); @@ -80,10 +172,26 @@ int cachedhcp_record ( userptr_t data, size_t max_len ) { dhcphdr = ( ( ( void * ) dhcppkt ) + sizeof ( *dhcppkt ) ); dhcppkt_init ( dhcppkt, dhcphdr, len ); - /* Store as cached DHCPACK, and mark original copy as consumed */ - DBGC ( colour, "CACHEDHCP found cached DHCPACK at %#08lx+%#zx/%#zx\n", + /* Discard duplicate packets, since some PXE stacks (including + * iPXE itself) will report the DHCPACK packet as the PXEBSACK + * if no separate PXEBSACK exists. + */ + for ( i = 0 ; i < ( sizeof ( cached_packets ) / + sizeof ( cached_packets[0] ) ) ; i++ ) { + tmp = cached_packets[i]->dhcppkt; + if ( tmp && ( dhcppkt_len ( tmp ) == len ) && + ( memcmp ( tmp->dhcphdr, dhcppkt->dhcphdr, len ) == 0 ) ) { + DBGC ( colour, "CACHEDHCP %s duplicates %s\n", + cache->name, cached_packets[i]->name ); + dhcppkt_put ( dhcppkt ); + return -EEXIST; + } + } + + /* Store as cached packet */ + DBGC ( colour, "CACHEDHCP %s at %#08lx+%#zx/%#zx\n", cache->name, user_to_phys ( data, 0 ), len, max_len ); - cached_dhcpack = dhcppkt; + cache->dhcppkt = dhcppkt; return 0; } @@ -94,14 +202,20 @@ int cachedhcp_record ( userptr_t data, size_t max_len ) { */ static void cachedhcp_startup ( void ) { - /* If cached DHCP packet was not claimed by any network device - * during startup, then free it. - */ - if ( cached_dhcpack ) { - DBGC ( colour, "CACHEDHCP freeing unclaimed cached DHCPACK\n" ); - dhcppkt_put ( cached_dhcpack ); - cached_dhcpack = NULL; + /* Apply cached ProxyDHCPOFFER, if any */ + cachedhcp_apply ( &cached_proxydhcp, NULL ); + + /* Apply cached PXEBSACK, if any */ + cachedhcp_apply ( &cached_pxebs, NULL ); + + /* Free any remaining cached packets */ + if ( cached_dhcpack.dhcppkt ) { + DBGC ( colour, "CACHEDHCP %s unclaimed\n", + cached_dhcpack.name ); } + cachedhcp_free ( &cached_dhcpack ); + cachedhcp_free ( &cached_proxydhcp ); + cachedhcp_free ( &cached_pxebs ); } /** Cached DHCPACK startup function */ @@ -117,38 +231,9 @@ struct startup_fn cachedhcp_startup_fn __startup_fn ( STARTUP_LATE ) = { * @ret rc Return status code */ static int cachedhcp_probe ( struct net_device *netdev ) { - struct ll_protocol *ll_protocol = netdev->ll_protocol; - int rc; - /* Do nothing unless we have a cached DHCPACK */ - if ( ! cached_dhcpack ) - return 0; - - /* Do nothing unless cached DHCPACK's MAC address matches this - * network device. - */ - if ( memcmp ( netdev->ll_addr, cached_dhcpack->dhcphdr->chaddr, - ll_protocol->ll_addr_len ) != 0 ) { - DBGC ( colour, "CACHEDHCP cached DHCPACK does not match %s\n", - netdev->name ); - return 0; - } - DBGC ( colour, "CACHEDHCP cached DHCPACK is for %s\n", netdev->name ); - - /* Register as DHCP settings for this network device */ - if ( ( rc = register_settings ( &cached_dhcpack->settings, - netdev_settings ( netdev ), - DHCP_SETTINGS_NAME ) ) != 0 ) { - DBGC ( colour, "CACHEDHCP could not register settings: %s\n", - strerror ( rc ) ); - return rc; - } - - /* Claim cached DHCPACK */ - dhcppkt_put ( cached_dhcpack ); - cached_dhcpack = NULL; - - return 0; + /* Apply cached DHCPACK to network device, if applicable */ + return cachedhcp_apply ( &cached_dhcpack, netdev ); } /** Cached DHCP packet network device driver */ |
