From b33cc1efe33ecf55017bb4ca7e8355e7b9c6e50c Mon Sep 17 00:00:00 2001 From: JuniorJPDJ Date: Thu, 8 Jul 2021 10:47:29 +0200 Subject: [build] Fix genfsimg to work with FATDIR with space Signed-off-by: Michael Brown --- src/util/genfsimg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/util/genfsimg b/src/util/genfsimg index 1635a11ac..c13158203 100755 --- a/src/util/genfsimg +++ b/src/util/genfsimg @@ -255,7 +255,7 @@ fi # Create FAT filesystem image, if applicable # if [ -n "${FATIMG}" ] ; then - FATSIZE=$(du -s -k ${FATDIR} | cut -f1) + FATSIZE=$(du -s -k "${FATDIR}" | cut -f1) FATSIZE=$(( FATSIZE + PAD + 256 )) touch "${FATIMG}" if [ "${FATSIZE}" -le "1440" ] ; then -- cgit v1.2.3-55-g7522 From db6310c3e5948a327a1c6f5706f727fd6cd67c70 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Mon, 26 Jul 2021 15:42:00 +0100 Subject: [efi] Use zero for PCI vendor/device IDs when no applicable ID exists When building an EFI ROM image for which no PCI vendor/device ID is applicable (e.g. bin-x86_64-efi/ipxe.efirom), the build process will currently construct a command such as ./util/efirom -v -d -c bin-x86_64-efi/ipxe.efidrv \ bin-x86_64-efi/ipxe.efirom which gets interpreted as a vendor ID of "-0xd" (i.e. 0xfff3, after truncation to 16 bits). Fix by using an explicit zero ID when no applicable ID exists, as is already done when constructing BIOS ROM images. Reported-by: Konstantin Aladyshev Signed-off-by: Michael Brown --- src/Makefile.efi | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/Makefile.efi b/src/Makefile.efi index 11e29dd58..bd479b3da 100644 --- a/src/Makefile.efi +++ b/src/Makefile.efi @@ -43,7 +43,8 @@ $(BIN)/%.drv.efi : $(BIN)/%.efidrv $(BIN)/%.efirom : $(BIN)/%.efidrv $(EFIROM) $(QM)$(ECHO) " [FINISH] $@" - $(Q)$(EFIROM) -v $(TGT_PCI_VENDOR) -d $(TGT_PCI_DEVICE) -c $< $@ + $(Q)$(EFIROM) -v $(firstword $(TGT_PCI_VENDOR) 0) \ + -d $(firstword $(TGT_PCI_DEVICE) 0) -c $< $@ $(BIN)/efidrv.cab : $(BIN)/alldrv.efis # $(ALL_drv.efi) is not yet defined $(QM)$(ECHO) " [CAB] $@" -- cgit v1.2.3-55-g7522 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/arch/x86/interface/pcbios/bios_cachedhcp.c | 3 +- src/core/cachedhcp.c | 175 ++++++++++++++++++------- src/include/ipxe/cachedhcp.h | 9 +- src/include/ipxe/dhcppkt.h | 2 +- src/interface/efi/efi_cachedhcp.c | 29 +++- 5 files changed, 167 insertions(+), 51 deletions(-) (limited to 'src') diff --git a/src/arch/x86/interface/pcbios/bios_cachedhcp.c b/src/arch/x86/interface/pcbios/bios_cachedhcp.c index 3d38699f7..277c40d6f 100644 --- a/src/arch/x86/interface/pcbios/bios_cachedhcp.c +++ b/src/arch/x86/interface/pcbios/bios_cachedhcp.c @@ -59,7 +59,8 @@ static void cachedhcp_init ( void ) { } /* Record cached DHCPACK */ - if ( ( rc = cachedhcp_record ( phys_to_user ( cached_dhcpack_phys ), + if ( ( rc = cachedhcp_record ( &cached_dhcpack, + phys_to_user ( cached_dhcpack_phys ), sizeof ( BOOTPLAYER_t ) ) ) != 0 ) { DBGC ( colour, "CACHEDHCP could not record DHCPACK: %s\n", strerror ( rc ) ); 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 */ 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 ); } diff --git a/src/interface/efi/efi_cachedhcp.c b/src/interface/efi/efi_cachedhcp.c index 14b531d09..1d4b98fd6 100644 --- a/src/interface/efi/efi_cachedhcp.c +++ b/src/interface/efi/efi_cachedhcp.c @@ -75,17 +75,40 @@ int efi_cachedhcp_record ( EFI_HANDLE device ) { /* Record DHCPACK, if present */ if ( mode->DhcpAckReceived && - ( ( rc = cachedhcp_record ( virt_to_user ( &mode->DhcpAck ), + ( ( rc = cachedhcp_record ( &cached_dhcpack, + virt_to_user ( &mode->DhcpAck ), sizeof ( mode->DhcpAck ) ) ) != 0 ) ) { DBGC ( device, "EFI %s could not record DHCPACK: %s\n", efi_handle_name ( device ), strerror ( rc ) ); - goto err_record; + goto err_dhcpack; + } + + /* Record ProxyDHCPOFFER, if present */ + if ( mode->ProxyOfferReceived && + ( ( rc = cachedhcp_record ( &cached_proxydhcp, + virt_to_user ( &mode->ProxyOffer ), + sizeof ( mode->ProxyOffer ) ) ) != 0)){ + DBGC ( device, "EFI %s could not record ProxyDHCPOFFER: %s\n", + efi_handle_name ( device ), strerror ( rc ) ); + goto err_proxydhcp; + } + + /* Record PxeBSACK, if present */ + if ( mode->PxeReplyReceived && + ( ( rc = cachedhcp_record ( &cached_pxebs, + virt_to_user ( &mode->PxeReply ), + sizeof ( mode->PxeReply ) ) ) != 0)){ + DBGC ( device, "EFI %s could not record PXEBSACK: %s\n", + efi_handle_name ( device ), strerror ( rc ) ); + goto err_pxebs; } /* Success */ rc = 0; - err_record: + err_pxebs: + err_proxydhcp: + err_dhcpack: err_ipv6: bs->CloseProtocol ( device, &efi_pxe_base_code_protocol_guid, efi_image_handle, NULL ); -- 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') 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') 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 From 91e147213c58c4c841416830122a0e44d69f69ad Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Wed, 8 Sep 2021 14:23:21 +0100 Subject: [ecm] Expose USB vendor/device information to ecm_fetch_mac() Signed-off-by: Michael Brown --- src/drivers/net/ecm.c | 11 ++++++----- src/drivers/net/ecm.h | 2 +- src/drivers/net/ncm.c | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/drivers/net/ecm.c b/src/drivers/net/ecm.c index 847a45b85..f531b04f8 100644 --- a/src/drivers/net/ecm.c +++ b/src/drivers/net/ecm.c @@ -81,13 +81,14 @@ ecm_ethernet_descriptor ( struct usb_configuration_descriptor *config, /** * Get hardware MAC address * - * @v usb USB device + * @v func USB function * @v desc Ethernet functional descriptor * @v hw_addr Hardware address to fill in * @ret rc Return status code */ -int ecm_fetch_mac ( struct usb_device *usb, +int ecm_fetch_mac ( struct usb_function *func, struct ecm_ethernet_descriptor *desc, uint8_t *hw_addr ) { + struct usb_device *usb = func->usb; char buf[ base16_encoded_len ( ETH_ALEN ) + 1 /* NUL */ ]; int len; int rc; @@ -103,7 +104,7 @@ int ecm_fetch_mac ( struct usb_device *usb, /* Sanity check */ if ( len != ( ( int ) ( sizeof ( buf ) - 1 /* NUL */ ) ) ) { DBGC ( usb, "USB %s has invalid ECM MAC \"%s\"\n", - usb->name, buf ); + func->name, buf ); return -EINVAL; } @@ -112,7 +113,7 @@ int ecm_fetch_mac ( struct usb_device *usb, if ( len < 0 ) { rc = len; DBGC ( usb, "USB %s could not decode ECM MAC \"%s\": %s\n", - usb->name, buf, strerror ( rc ) ); + func->name, buf, strerror ( rc ) ); return rc; } @@ -464,7 +465,7 @@ static int ecm_probe ( struct usb_function *func, } /* Fetch MAC address */ - if ( ( rc = ecm_fetch_mac ( usb, ethernet, netdev->hw_addr ) ) != 0 ) { + if ( ( rc = ecm_fetch_mac ( func, ethernet, netdev->hw_addr ) ) != 0 ) { DBGC ( ecm, "ECM %p could not fetch MAC address: %s\n", ecm, strerror ( rc ) ); goto err_fetch_mac; diff --git a/src/drivers/net/ecm.h b/src/drivers/net/ecm.h index 83d324bdc..0ad3ddb95 100644 --- a/src/drivers/net/ecm.h +++ b/src/drivers/net/ecm.h @@ -86,7 +86,7 @@ struct ecm_device { extern struct ecm_ethernet_descriptor * ecm_ethernet_descriptor ( struct usb_configuration_descriptor *config, struct usb_interface_descriptor *interface ); -extern int ecm_fetch_mac ( struct usb_device *usb, +extern int ecm_fetch_mac ( struct usb_function *func, struct ecm_ethernet_descriptor *desc, uint8_t *hw_addr ); diff --git a/src/drivers/net/ncm.c b/src/drivers/net/ncm.c index cc07a4388..1e8088d76 100644 --- a/src/drivers/net/ncm.c +++ b/src/drivers/net/ncm.c @@ -598,7 +598,7 @@ static int ncm_probe ( struct usb_function *func, } /* Fetch MAC address */ - if ( ( rc = ecm_fetch_mac ( usb, ethernet, netdev->hw_addr ) ) != 0 ) { + if ( ( rc = ecm_fetch_mac ( func, ethernet, netdev->hw_addr ) ) != 0 ) { DBGC ( ncm, "NCM %p could not fetch MAC address: %s\n", ncm, strerror ( rc ) ); goto err_fetch_mac; -- cgit v1.2.3-55-g7522 From 05a76acc6dc68bd962c12436ac9f48de1288fad2 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Wed, 8 Sep 2021 14:29:36 +0100 Subject: [ecm] Use ACPI-provided system-specific MAC address if present Use the "system MAC address" provided within the DSDT/SSDT if such an address is available and has not already been assigned to a network device. Tested-by: Andreas Hammarskjöld Signed-off-by: Michael Brown --- src/drivers/net/ecm.c | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'src') diff --git a/src/drivers/net/ecm.c b/src/drivers/net/ecm.c index f531b04f8..826b3b16d 100644 --- a/src/drivers/net/ecm.c +++ b/src/drivers/net/ecm.c @@ -30,6 +30,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include #include +#include #include #include "ecm.h" @@ -93,6 +94,14 @@ int ecm_fetch_mac ( struct usb_function *func, int len; int rc; + /* Use system-specific MAC address, if present and not already used */ + if ( ( ( rc = acpi_mac ( hw_addr ) ) == 0 ) && + ! find_netdev_by_ll_addr ( ðernet_protocol, hw_addr ) ) { + DBGC ( usb, "USB %s using system-specific MAC %s\n", + func->name, eth_ntoa ( hw_addr ) ); + return 0; + } + /* Fetch MAC address string */ len = usb_get_string_descriptor ( usb, desc->mac, 0, buf, sizeof ( buf ) ); -- cgit v1.2.3-55-g7522 From 2265a65191d76ce367913a61c97752ab88ab1a59 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Fri, 10 Sep 2021 15:34:57 +0100 Subject: [readline] Extend maximum read line length to 1024 characters Realistic Linux kernel command lines may exceed our current 256 character limit for interactively edited commands or settings. Switch from stack allocation to heap allocation, and increase the limit to 1024 characters. Requested-by: Matteo Guglielmi Signed-off-by: Michael Brown --- src/hci/readline.c | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/hci/readline.c b/src/hci/readline.c index 852c4503a..ecc72d43f 100644 --- a/src/hci/readline.c +++ b/src/hci/readline.c @@ -38,7 +38,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); * */ -#define READLINE_MAX 256 +#define READLINE_MAX 1024 /** * Synchronise console with edited string @@ -258,8 +258,8 @@ void history_free ( struct readline_history *history ) { int readline_history ( const char *prompt, const char *prefill, struct readline_history *history, unsigned long timeout, char **line ) { - char buf[READLINE_MAX]; struct edit_string string; + char *buf; int key; int move_by; const char *new_string; @@ -275,10 +275,14 @@ int readline_history ( const char *prompt, const char *prefill, /* Ensure cursor is visible */ printf ( "\033[?25h" ); - /* Initialise editable string */ + /* Allocate buffer and initialise editable string */ + buf = zalloc ( READLINE_MAX ); + if ( ! buf ) { + rc = -ENOMEM; + goto done; + } memset ( &string, 0, sizeof ( string ) ); - init_editstring ( &string, buf, sizeof ( buf ) ); - buf[0] = '\0'; + init_editstring ( &string, buf, READLINE_MAX ); /* Prefill string, if applicable */ if ( prefill ) { @@ -303,8 +307,13 @@ int readline_history ( const char *prompt, const char *prefill, switch ( key ) { case CR: case LF: - *line = strdup ( buf ); - rc = ( ( *line ) ? 0 : -ENOMEM ); + /* Shrink string (ignoring failures) */ + *line = realloc ( buf, + ( strlen ( buf ) + 1 /* NUL */ ) ); + if ( ! *line ) + *line = buf; + buf = NULL; + rc = 0; goto done; case CTRL_C: rc = -ECANCELED; @@ -332,6 +341,7 @@ int readline_history ( const char *prompt, const char *prefill, done: putchar ( '\n' ); + free ( buf ); if ( history ) { if ( *line && (*line)[0] ) history_append ( history, *line ); -- cgit v1.2.3-55-g7522