[acpi] Support the "_RTXMAC_" format for ACPI-based MAC addresses

Some newer HP products expose the host-based MAC (HBMAC) address using
an ACPI method named "RTMA" returning a part-binary string of the form
"_RTXMAC_#<mac>#", where "<mac>" comprises the raw MAC address bytes.

Extend the existing support to handle this format alongside the older
"_AUXMAC_" format (which uses a base16-encoded MAC address).

Signed-off-by: Michael Brown <mcb30@ipxe.org>
diff --git a/src/core/acpimac.c b/src/core/acpimac.c
index 1cc8220..5920480 100644
--- a/src/core/acpimac.c
+++ b/src/core/acpimac.c
@@ -46,11 +46,79 @@
 /** MACA signature */
 #define MACA_SIGNATURE ACPI_SIGNATURE ( 'M', 'A', 'C', 'A' )
 
-/** Maximum number of bytes to skip after AMAC/MACA signature
+/** 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 AUXMAC_MAX_SKIP 8
+#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
@@ -59,6 +127,7 @@
  * @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,
@@ -72,51 +141,44 @@
  * 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_#";
+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 auxmac[ sizeof ( prefix ) /* "_AUXMAC_#" */ +
-		     ( ETH_ALEN * 2 ) /* MAC */ + 1 /* "#" */ + 1 /* NUL */ ];
-	char *mac = &auxmac[ sizeof ( prefix ) ];
-	int decoded_len;
+	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 "_AUXMAC_#.....#" close to signature */
+	/* Scan for suitable string close to signature */
 	for ( skip = 0 ;
-	      ( ( skip < AUXMAC_MAX_SKIP ) &&
-		( offset + skip + sizeof ( auxmac ) ) < len ) ;
+	      ( ( skip < ACPIMAC_MAX_SKIP ) &&
+		( offset + skip + sizeof ( buf ) ) <= len ) ;
 	      skip++ ) {
 
 		/* Read value */
-		copy_from_user ( auxmac, zsdt, ( offset + skip ),
-				 sizeof ( auxmac ) );
+		copy_from_user ( buf, zsdt, ( offset + skip ),
+				 sizeof ( buf ) );
 
 		/* Check for expected format */
-		if ( memcmp ( auxmac, prefix, sizeof ( prefix ) ) != 0 )
+		if ( memcmp ( buf, extractor->prefix, prefix_len ) != 0 )
 			continue;
-		if ( auxmac[ sizeof ( auxmac ) - 2 ] != '#' )
+		if ( buf[ sizeof ( buf ) - 2 ] != '#' )
 			continue;
-		if ( auxmac[ sizeof ( auxmac ) - 1 ] != '\0' )
+		if ( buf[ sizeof ( buf ) - 1 ] != '\0' )
 			continue;
-		DBGC ( colour, "ACPI found MAC string \"%s\"\n", auxmac );
+		DBGC ( colour, "ACPI found MAC:\n" );
+		DBGC_HDA ( colour, ( offset + skip ), buf, sizeof ( buf ) );
 
 		/* Terminate MAC address string */
-		mac = &auxmac[ sizeof ( prefix ) ];
-		mac[ ETH_ALEN * 2 ] = '\0';
+		mac[extractor->len] = '\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 ) );
+		if ( ( rc = extractor->decode ( mac, hw_addr ) ) != 0 )
 			return rc;
-		}
 
 		/* Check MAC address validity */
 		if ( ! is_valid_ether_addr ( hw_addr ) ) {
@@ -132,6 +194,36 @@
 }
 
 /**
+ * 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
@@ -142,12 +234,17 @@
 
 	/* Look for an "AMAC" address */
 	if ( ( rc = acpi_extract ( AMAC_SIGNATURE, hw_addr,
-				   acpi_extract_mac ) ) == 0 )
+				   acpimac_extract_auxmac ) ) == 0 )
 		return 0;
 
 	/* Look for a "MACA" address */
 	if ( ( rc = acpi_extract ( MACA_SIGNATURE, hw_addr,
-				   acpi_extract_mac ) ) == 0 )
+				   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;
diff --git a/src/tests/acpi_test.c b/src/tests/acpi_test.c
index 972067e..1ca5bef 100644
--- a/src/tests/acpi_test.c
+++ b/src/tests/acpi_test.c
@@ -159,6 +159,24 @@
 ACPI_MAC ( maca, &maca_tables,
 	   DATA ( 0x52, 0x54, 0x00, 0x11, 0x22, 0x33 ) );
 
+/** "RTMA" SSDT */
+ACPI_TABLE ( rtma_ssdt, "SSDT",
+	     DATA ( 0x53, 0x53, 0x44, 0x54, 0x44, 0x00, 0x00, 0x00, 0x02,
+		    0x6d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		    0x00, 0x49, 0x4e, 0x54, 0x4c, 0x04, 0x06, 0x21, 0x20,
+		    0x10, 0x1f, 0x5c, 0x5f, 0x53, 0x42, 0x5f, 0x14, 0x18,
+		    0x52, 0x54, 0x4d, 0x41, 0x08, 0x0d, 0x5f, 0x52, 0x54,
+		    0x58, 0x4d, 0x41, 0x43, 0x5f, 0x23, 0x52, 0x54, 0x30,
+		    0x30, 0x30, 0x31, 0x23, 0x00 ) );
+
+/** "RTMA" test tables */
+ACPI_TABLES ( rtma_tables, &rtma_ssdt );
+
+/** "RTMA" test */
+ACPI_MAC ( rtma, &rtma_tables,
+	   DATA ( 0x52, 0x54, 0x30, 0x30, 0x30, 0x31 ) );
+
 /** Current ACPI test table set */
 static struct acpi_test_tables *acpi_test_tables;
 
@@ -229,6 +247,7 @@
 	/* MAC extraction tests */
 	acpi_mac_ok ( &amac );
 	acpi_mac_ok ( &maca );
+	acpi_mac_ok ( &rtma );
 }
 
 /** ACPI self-test */