| /* |
| * 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/settings.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' ) |
| |
| /** RTMA signature */ |
| #define RTMA_SIGNATURE ACPI_SIGNATURE ( 'R', 'T', 'M', 'A' ) |
| |
| /** Maximum number of bytes to skip after ACPI signature |
| * |
| * This is entirely empirical. |
| */ |
| #define ACPIMAC_MAX_SKIP 8 |
| |
| /** An ACPI MAC extraction mechanism */ |
| struct acpimac_extractor { |
| /** Prefix string */ |
| const char *prefix; |
| /** Encoded MAC length */ |
| size_t len; |
| /** Decode MAC |
| * |
| * @v mac Encoded MAC |
| * @v hw_addr MAC address to fill in |
| * @ret rc Return status code |
| */ |
| int ( * decode ) ( const char *mac, uint8_t *hw_addr ); |
| }; |
| |
| /** |
| * Decode Base16-encoded MAC address |
| * |
| * @v mac Encoded MAC |
| * @v hw_addr MAC address to fill in |
| * @ret rc Return status code |
| */ |
| static int acpimac_decode_base16 ( const char *mac, uint8_t *hw_addr ) { |
| int len; |
| int rc; |
| |
| /* Attempt to base16-decode MAC address */ |
| len = base16_decode ( mac, hw_addr, ETH_ALEN ); |
| if ( len < 0 ) { |
| rc = len; |
| DBGC ( colour, "ACPI could not decode base16 MAC \"%s\": %s\n", |
| mac, strerror ( rc ) ); |
| return rc; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Decode raw MAC address |
| * |
| * @v mac Encoded MAC |
| * @v hw_addr MAC address to fill in |
| * @ret rc Return status code |
| */ |
| static int acpimac_decode_raw ( const char *mac, uint8_t *hw_addr ) { |
| |
| memcpy ( hw_addr, mac, ETH_ALEN ); |
| return 0; |
| } |
| |
| /** "_AUXMAC_" extraction mechanism */ |
| static struct acpimac_extractor acpimac_auxmac = { |
| .prefix = "_AUXMAC_#", |
| .len = ( ETH_ALEN * 2 ), |
| .decode = acpimac_decode_base16, |
| }; |
| |
| /** "_RTXMAC_" extraction mechanism */ |
| static struct acpimac_extractor acpimac_rtxmac = { |
| .prefix = "_RTXMAC_#", |
| .len = ETH_ALEN, |
| .decode = acpimac_decode_raw, |
| }; |
| |
| /** |
| * 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 |
| * @v extractor ACPI MAC address extractor |
| * @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 acpimac_extract ( userptr_t zsdt, size_t len, size_t offset, |
| void *data, struct acpimac_extractor *extractor ){ |
| size_t prefix_len = strlen ( extractor->prefix ); |
| uint8_t *hw_addr = data; |
| size_t skip = 0; |
| char buf[ prefix_len + extractor->len + 1 /* "#" */ + 1 /* NUL */ ]; |
| char *mac = &buf[prefix_len]; |
| int rc; |
| |
| /* Skip signature and at least one tag byte */ |
| offset += ( 4 /* signature */ + 1 /* tag byte */ ); |
| |
| /* Scan for suitable string close to signature */ |
| for ( skip = 0 ; |
| ( ( skip < ACPIMAC_MAX_SKIP ) && |
| ( offset + skip + sizeof ( buf ) ) <= len ) ; |
| skip++ ) { |
| |
| /* Read value */ |
| copy_from_user ( buf, zsdt, ( offset + skip ), |
| sizeof ( buf ) ); |
| |
| /* Check for expected format */ |
| if ( memcmp ( buf, extractor->prefix, prefix_len ) != 0 ) |
| continue; |
| if ( buf[ sizeof ( buf ) - 2 ] != '#' ) |
| continue; |
| if ( buf[ sizeof ( buf ) - 1 ] != '\0' ) |
| continue; |
| DBGC ( colour, "ACPI found MAC:\n" ); |
| DBGC_HDA ( colour, ( offset + skip ), buf, sizeof ( buf ) ); |
| |
| /* Terminate MAC address string */ |
| mac[extractor->len] = '\0'; |
| |
| /* Decode MAC address */ |
| if ( ( rc = extractor->decode ( mac, hw_addr ) ) != 0 ) |
| 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 "_AUXMAC_" 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 |
| */ |
| static int acpimac_extract_auxmac ( userptr_t zsdt, size_t len, size_t offset, |
| void *data ) { |
| |
| return acpimac_extract ( zsdt, len, offset, data, &acpimac_auxmac ); |
| } |
| |
| /** |
| * Extract "_RTXMAC_" 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 |
| */ |
| static int acpimac_extract_rtxmac ( userptr_t zsdt, size_t len, size_t offset, |
| void *data ) { |
| |
| return acpimac_extract ( zsdt, len, offset, data, &acpimac_rtxmac ); |
| } |
| |
| /** |
| * 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, |
| acpimac_extract_auxmac ) ) == 0 ) |
| return 0; |
| |
| /* Look for a "MACA" address */ |
| if ( ( rc = acpi_extract ( MACA_SIGNATURE, hw_addr, |
| acpimac_extract_auxmac ) ) == 0 ) |
| return 0; |
| |
| /* Look for a "RTMA" address */ |
| if ( ( rc = acpi_extract ( RTMA_SIGNATURE, hw_addr, |
| acpimac_extract_rtxmac ) ) == 0 ) |
| return 0; |
| |
| return -ENOENT; |
| } |
| |
| /** |
| * Fetch system MAC address setting |
| * |
| * @v data Buffer to fill with setting data |
| * @v len Length of buffer |
| * @ret len Length of setting data, or negative error |
| */ |
| static int sysmac_fetch ( void *data, size_t len ) { |
| uint8_t mac[ETH_ALEN]; |
| int rc; |
| |
| /* Try fetching ACPI MAC address */ |
| if ( ( rc = acpi_mac ( mac ) ) != 0 ) |
| return rc; |
| |
| /* Return MAC address */ |
| if ( len > sizeof ( mac ) ) |
| len = sizeof ( mac ); |
| memcpy ( data, mac, len ); |
| return ( sizeof ( mac ) ); |
| } |
| |
| /** System MAC address setting */ |
| const struct setting sysmac_setting __setting ( SETTING_MISC, sysmac ) = { |
| .name = "sysmac", |
| .description = "System MAC", |
| .type = &setting_type_hex, |
| .scope = &builtin_scope, |
| }; |
| |
| /** System MAC address built-in setting */ |
| struct builtin_setting sysmac_builtin_setting __builtin_setting = { |
| .setting = &sysmac_setting, |
| .fetch = sysmac_fetch, |
| }; |