[efi] Provide VLAN configuration protocol

UEFI implements VLAN support within the Managed Network Protocol (MNP)
driver, which may create child VLAN devices automatically based on
stored UEFI variables.  These child devices do not themselves provide
a raw-packet interface via EFI_SIMPLE_NETWORK_PROTOCOL, and may be
consumed only via the EFI_MANAGED_NETWORK_PROTOCOL interface.

The device paths constructed for these child devices may conflict with
those for the EFI_SIMPLE_NETWORK_PROTOCOL instances that iPXE attempts
to install for its own VLAN devices.  The upshot is that creating an
iPXE VLAN device (e.g. via the "vcreate" command) will fail if the
UEFI Managed Network Protocol has already created a device for the
same VLAN tag.

Fix by providing our own EFI_VLAN_CONFIG_PROTOCOL instance on the same
device handle as EFI_SIMPLE_NETWORK_PROTOCOL.  This causes the MNP
driver to treat iPXE's device as supporting hardware VLAN offload, and
it will therefore not attempt to install its own instance of the
protocol.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
diff --git a/src/include/ipxe/efi/efi_null.h b/src/include/ipxe/efi/efi_null.h
index 2974570..d23d363 100644
--- a/src/include/ipxe/efi/efi_null.h
+++ b/src/include/ipxe/efi/efi_null.h
@@ -19,9 +19,11 @@
 #include <ipxe/efi/Protocol/PxeBaseCode.h>
 #include <ipxe/efi/Protocol/SimpleNetwork.h>
 #include <ipxe/efi/Protocol/UsbIo.h>
+#include <ipxe/efi/Protocol/VlanConfig.h>
 
 extern void efi_nullify_snp ( EFI_SIMPLE_NETWORK_PROTOCOL *snp );
 extern void efi_nullify_nii ( EFI_NETWORK_INTERFACE_IDENTIFIER_PROTOCOL *nii );
+extern void efi_nullify_vlan ( EFI_VLAN_CONFIG_PROTOCOL *vcfg );
 extern void efi_nullify_name2 ( EFI_COMPONENT_NAME2_PROTOCOL *name2 );
 extern void efi_nullify_load_file ( EFI_LOAD_FILE_PROTOCOL *load_file );
 extern void efi_nullify_hii ( EFI_HII_CONFIG_ACCESS_PROTOCOL *hii );
diff --git a/src/include/ipxe/efi/efi_snp.h b/src/include/ipxe/efi/efi_snp.h
index c278b1d..96373b5 100644
--- a/src/include/ipxe/efi/efi_snp.h
+++ b/src/include/ipxe/efi/efi_snp.h
@@ -19,6 +19,7 @@
 #include <ipxe/efi/Protocol/HiiConfigAccess.h>
 #include <ipxe/efi/Protocol/HiiDatabase.h>
 #include <ipxe/efi/Protocol/LoadFile.h>
+#include <ipxe/efi/Protocol/VlanConfig.h>
 
 /** SNP transmit completion ring size */
 #define EFI_SNP_NUM_TX 32
@@ -51,6 +52,8 @@
 	struct list_head rx;
 	/** The network interface identifier */
 	EFI_NETWORK_INTERFACE_IDENTIFIER_PROTOCOL nii;
+	/** VLAN configuration protocol */
+	EFI_VLAN_CONFIG_PROTOCOL vcfg;
 	/** Component name protocol */
 	EFI_COMPONENT_NAME2_PROTOCOL name2;
 	/** Load file protocol handle */
diff --git a/src/include/ipxe/vlan.h b/src/include/ipxe/vlan.h
index e4baf4c..8bf7923 100644
--- a/src/include/ipxe/vlan.h
+++ b/src/include/ipxe/vlan.h
@@ -74,6 +74,8 @@
 	return VLAN_TAG ( vlan_tci ( netdev ) );
 }
 
+extern struct net_device * vlan_find ( struct net_device *trunk,
+				       unsigned int tag );
 extern int vlan_can_be_trunk ( struct net_device *trunk );
 extern int vlan_create ( struct net_device *trunk, unsigned int tag,
 			 unsigned int priority );
diff --git a/src/interface/efi/efi_null.c b/src/interface/efi/efi_null.c
index 29ca5b9..d0f0428 100644
--- a/src/interface/efi/efi_null.c
+++ b/src/interface/efi/efi_null.c
@@ -195,6 +195,48 @@
 
 /******************************************************************************
  *
+ * VLAN configuration protocol
+ *
+ ******************************************************************************
+ */
+
+static EFI_STATUS EFIAPI
+efi_null_vlan_set ( EFI_VLAN_CONFIG_PROTOCOL *vcfg __unused,
+		    UINT16 tag __unused, UINT8 priority __unused ) {
+	return EFI_UNSUPPORTED;
+}
+
+static EFI_STATUS EFIAPI
+efi_null_vlan_find ( EFI_VLAN_CONFIG_PROTOCOL *vcfg __unused,
+		     UINT16 *filter __unused, UINT16 *count __unused,
+		     EFI_VLAN_FIND_DATA **entries __unused ) {
+	return EFI_UNSUPPORTED;
+}
+
+static EFI_STATUS EFIAPI
+efi_null_vlan_remove ( EFI_VLAN_CONFIG_PROTOCOL *vcfg __unused,
+		       UINT16 tag __unused ) {
+	return EFI_UNSUPPORTED;
+}
+
+static EFI_VLAN_CONFIG_PROTOCOL efi_null_vlan = {
+	.Set		= efi_null_vlan_set,
+	.Find		= efi_null_vlan_find,
+	.Remove		= efi_null_vlan_remove,
+};
+
+/**
+ * Nullify VLAN configuration interface
+ *
+ * @v vcfg		VLAN configuration protocol
+ */
+void efi_nullify_vlan ( EFI_VLAN_CONFIG_PROTOCOL *vcfg ) {
+
+	memcpy ( vcfg, &efi_null_vlan, sizeof ( *vcfg ) );
+}
+
+/******************************************************************************
+ *
  * Component name protocol
  *
  ******************************************************************************
diff --git a/src/interface/efi/efi_snp.c b/src/interface/efi/efi_snp.c
index 6649eb1..a57e1f0 100644
--- a/src/interface/efi/efi_snp.c
+++ b/src/interface/efi/efi_snp.c
@@ -1490,6 +1490,135 @@
 
 /******************************************************************************
  *
+ * VLAN configuration protocol
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Create or modify VLAN device
+ *
+ * @v vcfg		VLAN configuration protocol
+ * @v tag		VLAN tag
+ * @v priority		Default VLAN priority
+ * @ret efirc		EFI status code
+ */
+static EFI_STATUS EFIAPI efi_vlan_set ( EFI_VLAN_CONFIG_PROTOCOL *vcfg,
+					UINT16 tag, UINT8 priority ) {
+	struct efi_snp_device *snpdev =
+		container_of ( vcfg, struct efi_snp_device, vcfg );
+	struct net_device *trunk = snpdev->netdev;
+	int rc;
+
+	/* Create or modify VLAN device */
+	if ( ( rc = vlan_create ( trunk, tag, priority ) ) != 0 ) {
+		DBGC ( snpdev, "SNPDEV %p could not create VLAN tag %d: %s\n",
+		       snpdev, tag, strerror ( rc ) );
+		return EFIRC ( rc );
+	}
+
+	DBGC ( snpdev, "SNPDEV %p created VLAN tag %d priority %d\n",
+	       snpdev, tag, priority );
+	return 0;
+}
+
+/**
+ * Find VLAN device(s)
+ *
+ * @v vcfg		VLAN configuration protocol
+ * @v filter		VLAN tag, or NULL to find all VLANs
+ * @v count		Number of VLANs
+ * @v entries		List of VLANs
+ * @ret efirc		EFI status code
+ */
+static EFI_STATUS EFIAPI efi_vlan_find ( EFI_VLAN_CONFIG_PROTOCOL *vcfg,
+					 UINT16 *filter, UINT16 *count,
+					 EFI_VLAN_FIND_DATA **entries ) {
+	EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+	struct efi_snp_device *snpdev =
+		container_of ( vcfg, struct efi_snp_device, vcfg );
+	struct net_device *trunk = snpdev->netdev;
+	struct net_device *vlan;
+	EFI_VLAN_FIND_DATA *entry;
+	VOID *buffer;
+	unsigned int tag;
+	unsigned int tci;
+	size_t len;
+	EFI_STATUS efirc;
+
+	/* Count number of matching VLANs */
+	*count = 0;
+	for ( tag = 1 ; VLAN_TAG_IS_VALID ( tag ) ; tag++ ) {
+		if ( filter && ( tag != *filter ) )
+			continue;
+		if ( ! ( vlan = vlan_find ( trunk, tag ) ) )
+			continue;
+		(*count)++;
+	}
+
+	/* Allocate buffer to hold results */
+	len = ( (*count) * sizeof ( *entry ) );
+	if ( ( efirc = bs->AllocatePool ( EfiBootServicesData, len,
+					  &buffer ) ) != 0 )
+		return efirc;
+
+	/* Fill in buffer */
+	*entries = buffer;
+	entry = *entries;
+	for ( tag = 1 ; VLAN_TAG_IS_VALID ( tag ) ; tag++ ) {
+		if ( filter && ( tag != *filter ) )
+			continue;
+		if ( ! ( vlan = vlan_find ( trunk, tag ) ) )
+			continue;
+		tci = vlan_tci ( vlan );
+		entry->VlanId = VLAN_TAG ( tci );
+		entry->Priority = VLAN_PRIORITY ( tci );
+		assert ( entry->VlanId == tag );
+		entry++;
+	}
+	assert ( entry == &(*entries)[*count] );
+
+	return 0;
+}
+
+/**
+ * Remove VLAN device
+ *
+ * @v vcfg		VLAN configuration protocol
+ * @v tag		VLAN tag
+ * @ret efirc		EFI status code
+ */
+static EFI_STATUS EFIAPI efi_vlan_remove ( EFI_VLAN_CONFIG_PROTOCOL *vcfg,
+					   UINT16 tag ) {
+	struct efi_snp_device *snpdev =
+		container_of ( vcfg, struct efi_snp_device, vcfg );
+	struct net_device *trunk = snpdev->netdev;
+	struct net_device *vlan;
+
+	/* Identify VLAN device */
+	vlan = vlan_find ( trunk, tag );
+	if ( ! vlan ) {
+		DBGC ( snpdev, "SNPDEV %p could not find VLAN tag %d\n",
+		       snpdev, tag );
+		return EFI_NOT_FOUND;
+	}
+
+	/* Remove VLAN device */
+	vlan_destroy ( vlan );
+
+	DBGC ( snpdev, "SNPDEV %p removed VLAN tag %d\n", snpdev, tag );
+	return 0;
+}
+
+/** VLAN configuration protocol */
+static EFI_VLAN_CONFIG_PROTOCOL efi_vlan = {
+	.Set		= efi_vlan_set,
+	.Find		= efi_vlan_find,
+	.Remove		= efi_vlan_remove,
+};
+
+/******************************************************************************
+ *
  * Component name protocol
  *
  ******************************************************************************
@@ -1687,6 +1816,9 @@
 	efi_snp_undi.Fudge -= efi_undi_checksum ( &efi_snp_undi,
 						  sizeof ( efi_snp_undi ) );
 
+	/* Populate the VLAN configuration protocol */
+	memcpy ( &snpdev->vcfg, &efi_vlan, sizeof ( snpdev->vcfg ) );
+
 	/* Populate the component name structure */
 	efi_snprintf ( snpdev->driver_name,
 		       ( sizeof ( snpdev->driver_name ) /
@@ -1725,6 +1857,7 @@
 			&efi_device_path_protocol_guid, snpdev->path,
 			&efi_nii_protocol_guid, &snpdev->nii,
 			&efi_nii31_protocol_guid, &snpdev->nii,
+			&efi_vlan_config_protocol_guid, &snpdev->vcfg,
 			&efi_component_name2_protocol_guid, &snpdev->name2,
 			&efi_load_file_protocol_guid, &snpdev->load_file,
 			NULL ) ) != 0 ) {
@@ -1811,6 +1944,7 @@
 			&efi_device_path_protocol_guid, snpdev->path,
 			&efi_nii_protocol_guid, &snpdev->nii,
 			&efi_nii31_protocol_guid, &snpdev->nii,
+			&efi_vlan_config_protocol_guid, &snpdev->vcfg,
 			&efi_component_name2_protocol_guid, &snpdev->name2,
 			&efi_load_file_protocol_guid, &snpdev->load_file,
 			NULL ) ) != 0 ) {
@@ -1820,6 +1954,7 @@
 	}
 	efi_nullify_snp ( &snpdev->snp );
 	efi_nullify_nii ( &snpdev->nii );
+	efi_nullify_vlan ( &snpdev->vcfg );
 	efi_nullify_name2 ( &snpdev->name2 );
 	efi_nullify_load_file ( &snpdev->load_file );
  err_install_protocol_interface:
diff --git a/src/net/vlan.c b/src/net/vlan.c
index fe44886..81ec623 100644
--- a/src/net/vlan.c
+++ b/src/net/vlan.c
@@ -199,8 +199,7 @@
  * @v tag		VLAN tag
  * @ret netdev		VLAN device, if any
  */
-static struct net_device * vlan_find ( struct net_device *trunk,
-				       unsigned int tag ) {
+struct net_device * vlan_find ( struct net_device *trunk, unsigned int tag ) {
 	struct net_device *netdev;
 	struct vlan_device *vlan;