[efi] Bind to only the topmost instance of the SNP or NII protocols

UEFI has the mildly annoying habit of installing copies of the
EFI_SIMPLE_NETWORK_PROTOCOL instance on the IPv4 and IPv6 child device
handles.  This can cause iPXE's SNP driver to attempt to bind to a
copy of the EFI_SIMPLE_NETWORK_PROTOCOL that iPXE itself provided on a
different handle.

Fix by refusing to bind to an SNP (or NII) handle if there exists
another instance of the same protocol further up the device path (on
the basis that we always want to bind to the highest possible device).

Signed-off-by: Michael Brown <mcb30@ipxe.org>
diff --git a/src/drivers/net/efi/snp.c b/src/drivers/net/efi/snp.c
index fbd6069..1920cdb 100644
--- a/src/drivers/net/efi/snp.c
+++ b/src/drivers/net/efi/snp.c
@@ -27,6 +27,7 @@
 #include <ipxe/efi/efi.h>
 #include <ipxe/efi/efi_driver.h>
 #include <ipxe/efi/efi_snp.h>
+#include <ipxe/efi/efi_utils.h>
 #include "snpnet.h"
 #include "nii.h"
 
@@ -40,31 +41,46 @@
  * Check to see if driver supports a device
  *
  * @v device		EFI device handle
+ * @v protocol		Protocol GUID
  * @ret rc		Return status code
  */
-static int snp_supported ( EFI_HANDLE device ) {
+static int snp_nii_supported ( EFI_HANDLE device, EFI_GUID *protocol ) {
 	EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+	EFI_HANDLE parent;
 	EFI_STATUS efirc;
+	int rc;
 
 	/* Check that this is not a device we are providing ourselves */
 	if ( find_snpdev ( device ) != NULL ) {
-		DBGCP ( device, "SNP %s is provided by this binary\n",
+		DBGCP ( device, "HANDLE %s is provided by this binary\n",
 			efi_handle_name ( device ) );
 		return -ENOTTY;
 	}
 
-	/* Test for presence of simple network protocol */
-	if ( ( efirc = bs->OpenProtocol ( device,
-					  &efi_simple_network_protocol_guid,
+	/* Test for presence of protocol */
+	if ( ( efirc = bs->OpenProtocol ( device, protocol,
 					  NULL, efi_image_handle, device,
 					  EFI_OPEN_PROTOCOL_TEST_PROTOCOL))!=0){
-		DBGCP ( device, "SNP %s is not an SNP device\n",
-			efi_handle_name ( device ) );
+		DBGCP ( device, "HANDLE %s is not a %s device\n",
+			efi_handle_name ( device ),
+			efi_guid_ntoa ( protocol ) );
 		return -EEFI ( efirc );
 	}
-	DBGC ( device, "SNP %s is an SNP device\n",
-	       efi_handle_name ( device ) );
 
+	/* Check that there are no instances of this protocol further
+	 * up this device path.
+	 */
+	if ( ( rc = efi_locate_device ( device, protocol,
+					&parent, 1 ) ) == 0 ) {
+		DBGC2 ( device, "HANDLE %s has %s-supporting parent ",
+			efi_handle_name ( device ),
+			efi_guid_ntoa ( protocol ) );
+		DBGC2 ( device, "%s\n", efi_handle_name ( parent ) );
+		return -ENOTTY;
+	}
+
+	DBGC ( device, "HANDLE %s is a %s device\n",
+	       efi_handle_name ( device ), efi_guid_ntoa ( protocol ) );
 	return 0;
 }
 
@@ -74,30 +90,20 @@
  * @v device		EFI device handle
  * @ret rc		Return status code
  */
+static int snp_supported ( EFI_HANDLE device ) {
+
+	return snp_nii_supported ( device, &efi_simple_network_protocol_guid );
+}
+
+/**
+ * Check to see if driver supports a device
+ *
+ * @v device		EFI device handle
+ * @ret rc		Return status code
+ */
 static int nii_supported ( EFI_HANDLE device ) {
-	EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
-	EFI_STATUS efirc;
 
-	/* Check that this is not a device we are providing ourselves */
-	if ( find_snpdev ( device ) != NULL ) {
-		DBGCP ( device, "NII %s is provided by this binary\n",
-			efi_handle_name ( device ) );
-		return -ENOTTY;
-	}
-
-	/* Test for presence of NII protocol */
-	if ( ( efirc = bs->OpenProtocol ( device,
-					  &efi_nii31_protocol_guid,
-					  NULL, efi_image_handle, device,
-					  EFI_OPEN_PROTOCOL_TEST_PROTOCOL))!=0){
-		DBGCP ( device, "NII %s is not an NII device\n",
-			efi_handle_name ( device ) );
-		return -EEFI ( efirc );
-	}
-	DBGC ( device, "NII %s is an NII device\n",
-	       efi_handle_name ( device ) );
-
-	return 0;
+	return snp_nii_supported ( device, &efi_nii31_protocol_guid );
 }
 
 /** EFI SNP driver */