/* * 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 #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' ) /** 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, };