summaryrefslogtreecommitdiffstats
path: root/src/core
diff options
context:
space:
mode:
authorSimon Rettberg2021-10-05 11:32:08 +0200
committerSimon Rettberg2021-10-05 11:32:08 +0200
commit207263d53a2c9ab9fe6e394ca0163eee3552984e (patch)
treef5ec9df709f2223007a5a6e5d505a929f66d4dc4 /src/core
parentMerge branch 'master' into openslx (diff)
parent[readline] Extend maximum read line length to 1024 characters (diff)
downloadipxe-207263d53a2c9ab9fe6e394ca0163eee3552984e.tar.gz
ipxe-207263d53a2c9ab9fe6e394ca0163eee3552984e.tar.xz
ipxe-207263d53a2c9ab9fe6e394ca0163eee3552984e.zip
Merge branch 'master' into openslx
Diffstat (limited to 'src/core')
-rw-r--r--src/core/acpi.c94
-rw-r--r--src/core/acpimac.c154
-rw-r--r--src/core/cachedhcp.c175
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 */