uefi: net: Ignore device paths which are subpaths of already seen devices.

Some devices have sub devices which have a simple network protocol installed
on them, but are not actually functional network devices and are apparently
instead places to hold ipv4 and ipv6 configuration information. Gigaboot deals
with this by comparing device paths and ignoring any devices which are
strictly subpaths of the last device which was successfully recognized. This
is an imperfect check since it relies on the order things are returned in, and
it would also be nice to better understand where these extra devices are coming
from, what they're for, and how they're officially supposed to be handled.
Absent that, this avoids having a bunch of network devices hanging around that
don't actually work.

Change-Id: I01179d2b452c964a80dd101aa9b6e3519518145e
diff --git a/src/drivers/net/uefi.c b/src/drivers/net/uefi.c
index 9de5db1..c5e7963 100644
--- a/src/drivers/net/uefi.c
+++ b/src/drivers/net/uefi.c
@@ -33,9 +33,17 @@
 #include "drivers/net/net.h"
 #include "net/ethernet.h"
 
+#include "uefi/edk/Protocol/DevicePath.h"
+#include "uefi/edk/Protocol/DevicePathToText.h"
 #include "uefi/edk/Protocol/SimpleNetwork.h"
 #include "uefi/uefi.h"
 
+static EFI_GUID uefi_device_path_protocol_guid =
+	EFI_DEVICE_PATH_PROTOCOL_GUID;
+
+static EFI_GUID uefi_device_path_to_text_protocol_guid =
+	EFI_DEVICE_PATH_TO_TEXT_PROTOCOL_GUID;
+
 static EFI_GUID uefi_simple_network_protocol_guid =
 	EFI_SIMPLE_NETWORK_PROTOCOL_GUID;
 
@@ -246,6 +254,29 @@
 	UINTN buf_size = 0;
 	EFI_HANDLE dummy_handle;
 	EFI_STATUS status = bs->LocateHandle(
+		ByProtocol, &uefi_device_path_to_text_protocol_guid, NULL,
+		&buf_size, &dummy_handle);
+	die_if(status != EFI_BUFFER_TOO_SMALL,
+	       "Error retrieving device path to text protocol handles.\n");
+
+	EFI_HANDLE *dp2text_handles = xmalloc(buf_size);
+
+	status = bs->LocateHandle(
+		ByProtocol, &uefi_device_path_to_text_protocol_guid, NULL,
+		&buf_size, dp2text_handles);
+	die_if(status != EFI_SUCCESS,
+	       "Error retrieving device path to text protocol handles.\n");
+
+	EFI_DEVICE_PATH_TO_TEXT_PROTOCOL *dp2text;
+	bs->HandleProtocol(dp2text_handles[0],
+		&uefi_device_path_to_text_protocol_guid, (void **)&dp2text);
+	die_if(status != EFI_SUCCESS,
+	       "Failed to get device path to text protocol.\n");
+	free(dp2text_handles);
+
+
+	buf_size = 0;
+	status = bs->LocateHandle(
 		ByProtocol, &uefi_simple_network_protocol_guid, NULL,
 		&buf_size, &dummy_handle);
 
@@ -256,6 +287,7 @@
 	       "Error retrieving network protocol handles.\n");
 
 	EFI_HANDLE *handles = xmalloc(buf_size);
+	int handle_count = buf_size / sizeof(dummy_handle);
 
 	status = bs->LocateHandle(
 		ByProtocol, &uefi_simple_network_protocol_guid, NULL,
@@ -263,15 +295,55 @@
 	die_if(status != EFI_SUCCESS,
 	       "Failed to retrieve network protocol handles.\n");
 
-	int handle_count = buf_size / sizeof(dummy_handle);
+	uint16_t *last_path = NULL;
 	for (int i = 0; i < handle_count; i++) {
 		EFI_SIMPLE_NETWORK_PROTOCOL *snp;
 		status = bs->HandleProtocol(handles[i],
 			&uefi_simple_network_protocol_guid, (void **)&snp);
 		die_if(status != EFI_SUCCESS,
 		       "Failed to get network protocol.\n");
+
+		EFI_DEVICE_PATH_PROTOCOL *dev_path;
+		status = bs->HandleProtocol(handles[i],
+			&uefi_device_path_protocol_guid, (void **)&dev_path);
+		if (status != EFI_SUCCESS) {
+			printf("Failed to get device path.\n");
+			continue;
+		}
+
+		/*
+		 * If the device path of this device is a subpath of the one
+		 * we've just seen, ignore it. Apparently certain devices
+		 * (e1000 was given as an example) have additional device
+		 * paths for their ipv4 and ipv6 configuration information.
+		 */
+		uint16_t *this_path = dp2text->ConvertDevicePathToText(
+			dev_path, 0, 0);
+		if (!this_path) {
+			printf("Failed to convert device path to text.\n");
+			continue;
+		}
+
+		if (last_path) {
+			int matches = 1;
+			for (size_t j = 0;
+			     matches && last_path[j] && this_path[j]; j++) {
+				if (last_path[j] != this_path[j])
+					matches = 0;
+			}
+
+			if (matches)
+				continue;
+		}
+
+		if (last_path)
+			bs->FreePool(last_path);
+		last_path = this_path;
 		uefi_net_create(snp);
 	}
+
+	if (last_path)
+		bs->FreePool(last_path);
 }
 
 static NetPoller uefinet_poller = {