]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[efi] Implement the EFI_PXE_BASE_CODE_PROTOCOL
authorMichael Brown <mcb30@ipxe.org>
Tue, 1 Sep 2015 20:23:34 +0000 (21:23 +0100)
committerMichael Brown <mcb30@ipxe.org>
Wed, 2 Sep 2015 12:45:12 +0000 (13:45 +0100)
Many UEFI NBPs expect to find an EFI_PXE_BASE_CODE_PROTOCOL installed
in addition to the EFI_SIMPLE_NETWORK_PROTOCOL.  Most NBPs use the
EFI_PXE_BASE_CODE_PROTOCOL only to retrieve the cached DHCP packets.

This implementation has been tested with grub.efi, shim.efi,
syslinux.efi, and wdsmgfw.efi.  Some methods (such as Discover() and
Arp()) are not used by any known NBP and so have not (yet) been
implemented.

Usage notes for the tested bootstraps are:

  - grub.efi uses EFI_PXE_BASE_CODE_PROTOCOL only to retrieve the
    cached DHCP packet, and uses no other methods.

  - shim.efi uses EFI_PXE_BASE_CODE_PROTOCOL to retrieve the cached
    DHCP packet and to retrieve the next NBP via the Mtftp() method.
    If shim.efi was downloaded via HTTP (or other non-TFTP protocol)
    then shim.efi will blindly call Mtftp() with an HTTP URI as the
    filename: this allows the next NBP (e.g. grubx64.efi) to also be
    transparently retrieved by HTTP.

    shim.efi can also use the EFI_SIMPLE_FILE_SYSTEM_PROTOCOL to
    retrieve files previously loaded by "imgfetch" or similar commands
    in iPXE.  The current implementation of shim.efi will use the
    EFI_SIMPLE_FILE_SYSTEM_PROTOCOL only if it does not find an
    EFI_PXE_BASE_CODE_PROTOCOL; this patch therefore prevents this
    usage of our EFI_SIMPLE_FILE_SYSTEM_PROTOCOL.  This logic could be
    trivially reversed in shim.efi if needed.

  - syslinux.efi uses EFI_PXE_BASE_CODE_PROTOCOL only to retrieve the
    cached DHCP packet.  Versions 6.03 and earlier have a bug which
    may cause syslinux.efi to attach to the wrong NIC if there are
    multiple NICs in the system (or if the UEFI firmware supports
    IPv6).

  - wdsmgfw.efi (ab)uses EFI_PXE_BASE_CODE_PROTOCOL to retrieve the
    cached DHCP packets, and to send and retrieve UDP packets via the
    UdpWrite() and UdpRead() methods.  (This was presumably done in
    order to minimise the amount of benefit obtainable by switching to
    UEFI, by replicating all of the design mistakes present in the
    original PXE specification.)

The EFI_DOWNGRADE_UX configuration option remains available for now,
until this implementation has received more widespread testing.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
src/image/efi_image.c
src/include/ipxe/efi/efi_pxe.h [new file with mode: 0644]
src/include/ipxe/errfile.h
src/interface/efi/efi_pxe.c [new file with mode: 0644]

index b7d8f9c6e649d22208edb6135170eb735d6070bd..89d57bbda4e74dbd63e6e7d3eeec4d72d202847d 100644 (file)
@@ -29,6 +29,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
 #include <ipxe/efi/efi_utils.h>
 #include <ipxe/efi/efi_strings.h>
 #include <ipxe/efi/efi_wrap.h>
+#include <ipxe/efi/efi_pxe.h>
 #include <ipxe/image.h>
 #include <ipxe/init.h>
 #include <ipxe/features.h>
@@ -159,6 +160,13 @@ static int efi_image_exec ( struct image *image ) {
                goto err_file_install;
        }
 
+       /* Install PXE base code protocol */
+       if ( ( rc = efi_pxe_install ( snpdev->handle, snpdev->netdev ) ) != 0 ){
+               DBGC ( image, "EFIIMAGE %p could not install PXE protocol: "
+                      "%s\n", image, strerror ( rc ) );
+               goto err_pxe_install;
+       }
+
        /* Install iPXE download protocol */
        if ( ( rc = efi_download_install ( snpdev->handle ) ) != 0 ) {
                DBGC ( image, "EFIIMAGE %p could not install iPXE download "
@@ -266,6 +274,8 @@ static int efi_image_exec ( struct image *image ) {
  err_image_path:
        efi_download_uninstall ( snpdev->handle );
  err_download_install:
+       efi_pxe_uninstall ( snpdev->handle );
+ err_pxe_install:
        efi_file_uninstall ( snpdev->handle );
  err_file_install:
  err_no_snpdev:
diff --git a/src/include/ipxe/efi/efi_pxe.h b/src/include/ipxe/efi/efi_pxe.h
new file mode 100644 (file)
index 0000000..b356f37
--- /dev/null
@@ -0,0 +1,17 @@
+#ifndef _IPXE_EFI_PXE_H
+#define _IPXE_EFI_PXE_H
+
+/** @file
+ *
+ * EFI PXE base code protocol
+ */
+
+#include <ipxe/efi/efi.h>
+#include <ipxe/netdevice.h>
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+extern int efi_pxe_install ( EFI_HANDLE handle, struct net_device *netdev );
+extern void efi_pxe_uninstall ( EFI_HANDLE handle );
+
+#endif /* _IPXE_EFI_PXE_H */
index e21c9593860189d0d5a9a3ec92c8bf9ff3b2593a..00f8f981f44c9caf7f7cb03a25d9ab50f2db077e 100644 (file)
@@ -339,6 +339,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
 #define ERRFILE_vmbus                ( ERRFILE_OTHER | 0x00470000 )
 #define ERRFILE_efi_time             ( ERRFILE_OTHER | 0x00480000 )
 #define ERRFILE_efi_watchdog         ( ERRFILE_OTHER | 0x00490000 )
+#define ERRFILE_efi_pxe                      ( ERRFILE_OTHER | 0x004a0000 )
 
 /** @} */
 
diff --git a/src/interface/efi/efi_pxe.c b/src/interface/efi/efi_pxe.c
new file mode 100644 (file)
index 0000000..1847e3f
--- /dev/null
@@ -0,0 +1,1599 @@
+/*
+ * Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * 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 (at your option) 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 <string.h>
+#include <errno.h>
+#include <ipxe/refcnt.h>
+#include <ipxe/list.h>
+#include <ipxe/netdevice.h>
+#include <ipxe/fakedhcp.h>
+#include <ipxe/process.h>
+#include <ipxe/uri.h>
+#include <ipxe/in.h>
+#include <ipxe/socket.h>
+#include <ipxe/tcpip.h>
+#include <ipxe/xferbuf.h>
+#include <ipxe/open.h>
+#include <ipxe/dhcppkt.h>
+#include <ipxe/udp.h>
+#include <ipxe/efi/efi.h>
+#include <ipxe/efi/efi_snp.h>
+#include <ipxe/efi/efi_pxe.h>
+#include <ipxe/efi/Protocol/PxeBaseCode.h>
+#include <usr/ifmgmt.h>
+#include <config/general.h>
+
+/** @file
+ *
+ * EFI PXE base code protocol
+ *
+ */
+
+/* Downgrade user experience if configured to do so
+ *
+ * See comments in efi_snp.c
+ */
+#ifdef EFI_DOWNGRADE_UX
+static EFI_GUID dummy_pxe_base_code_protocol_guid = {
+       0x70647523, 0x2320, 0x7477,
+       { 0x66, 0x20, 0x23, 0x6d, 0x6f, 0x72, 0x6f, 0x6e }
+};
+#define efi_pxe_base_code_protocol_guid dummy_pxe_base_code_protocol_guid
+#endif
+
+/** A PXE base code */
+struct efi_pxe {
+       /** Reference count */
+       struct refcnt refcnt;
+       /** Underlying network device */
+       struct net_device *netdev;
+       /** Name */
+       const char *name;
+       /** List of PXE base codes */
+       struct list_head list;
+
+       /** Installed handle */
+       EFI_HANDLE handle;
+       /** PXE base code protocol */
+       EFI_PXE_BASE_CODE_PROTOCOL base;
+       /** PXE base code mode */
+       EFI_PXE_BASE_CODE_MODE mode;
+
+       /** TCP/IP network-layer protocol */
+       struct tcpip_net_protocol *tcpip;
+       /** Network-layer protocol */
+       struct net_protocol *net;
+
+       /** Data transfer buffer */
+       struct xfer_buffer buf;
+
+       /** (M)TFTP download interface */
+       struct interface tftp;
+       /** Block size (for TFTP) */
+       size_t blksize;
+       /** Overall return status */
+       int rc;
+
+       /** UDP interface */
+       struct interface udp;
+       /** List of received UDP packets */
+       struct list_head queue;
+       /** UDP interface closer process */
+       struct process process;
+};
+
+/**
+ * Free PXE base code
+ *
+ * @v refcnt           Reference count
+ */
+static void efi_pxe_free ( struct refcnt *refcnt ) {
+       struct efi_pxe *pxe = container_of ( refcnt, struct efi_pxe, refcnt );
+
+       netdev_put ( pxe->netdev );
+       free ( pxe );
+}
+
+/** List of PXE base codes */
+static LIST_HEAD ( efi_pxes );
+
+/**
+ * Locate PXE base code
+ *
+ * @v handle           EFI handle
+ * @ret pxe            PXE base code, or NULL
+ */
+static struct efi_pxe * efi_pxe_find ( EFI_HANDLE handle ) {
+       struct efi_pxe *pxe;
+
+       /* Locate base code */
+       list_for_each_entry ( pxe, &efi_pxes, list ) {
+               if ( pxe->handle == handle )
+                       return pxe;
+       }
+
+       return NULL;
+}
+
+/******************************************************************************
+ *
+ * IP addresses
+ *
+ ******************************************************************************
+ */
+
+/**
+ * An EFI socket address
+ *
+ */
+struct sockaddr_efi {
+       /** Socket address family (part of struct @c sockaddr) */
+       sa_family_t se_family;
+       /** Flags (part of struct @c sockaddr_tcpip) */
+       uint16_t se_flags;
+       /** TCP/IP port (part of struct @c sockaddr_tcpip) */
+       uint16_t se_port;
+       /** Scope ID (part of struct @c sockaddr_tcpip)
+        *
+        * For link-local or multicast addresses, this is the network
+        * device index.
+        */
+        uint16_t se_scope_id;
+       /** IP address */
+       EFI_IP_ADDRESS se_addr;
+       /** Padding
+        *
+        * This ensures that a struct @c sockaddr_tcpip is large
+        * enough to hold a socket address for any TCP/IP address
+        * family.
+        */
+       char pad[ sizeof ( struct sockaddr ) -
+                 ( sizeof ( sa_family_t ) /* se_family */ +
+                   sizeof ( uint16_t ) /* se_flags */ +
+                   sizeof ( uint16_t ) /* se_port */ +
+                   sizeof ( uint16_t ) /* se_scope_id */ +
+                   sizeof ( EFI_IP_ADDRESS ) /* se_addr */ ) ];
+} __attribute__ (( packed, may_alias ));
+
+/**
+ * Populate socket address from EFI IP address
+ *
+ * @v pxe              PXE base code
+ * @v ip               EFI IP address
+ * @v sa               Socket address to fill in
+ */
+static void efi_pxe_ip_sockaddr ( struct efi_pxe *pxe, EFI_IP_ADDRESS *ip,
+                                 struct sockaddr *sa ) {
+       union {
+               struct sockaddr sa;
+               struct sockaddr_efi se;
+       } *sockaddr = container_of ( sa, typeof ( *sockaddr ), sa );
+
+       /* Initialise socket address */
+       memset ( sockaddr, 0, sizeof ( *sockaddr ) );
+       sockaddr->sa.sa_family = pxe->tcpip->sa_family;
+       memcpy ( &sockaddr->se.se_addr, ip, pxe->net->net_addr_len );
+       sockaddr->se.se_scope_id = pxe->netdev->index;
+}
+
+/**
+ * Transcribe EFI IP address (for debugging)
+ *
+ * @v pxe              PXE base code
+ * @v ip               EFI IP address
+ * @ret text           Transcribed IP address
+ */
+static const char * efi_pxe_ip_ntoa ( struct efi_pxe *pxe,
+                                     EFI_IP_ADDRESS *ip ) {
+
+       return pxe->net->ntoa ( ip );
+}
+
+/**
+ * Populate local IP address
+ *
+ * @v pxe              PXE base code
+ * @ret rc             Return status code
+ */
+static int efi_pxe_ip ( struct efi_pxe *pxe ) {
+       EFI_PXE_BASE_CODE_MODE *mode = &pxe->mode;
+       struct in_addr address;
+       struct in_addr netmask;
+
+       /* It's unclear which of the potentially many IPv6 addresses
+        * is supposed to be used.
+        */
+       if ( mode->UsingIpv6 )
+               return -ENOTSUP;
+
+       /* Fetch IP address and subnet mask */
+       fetch_ipv4_setting ( netdev_settings ( pxe->netdev ), &ip_setting,
+                            &address );
+       fetch_ipv4_setting ( netdev_settings ( pxe->netdev ), &netmask_setting,
+                            &netmask );
+
+       /* Populate IP address and subnet mask */
+       memset ( &mode->StationIp, 0, sizeof ( mode->StationIp ) );
+       memcpy ( &mode->StationIp, &address, sizeof ( address ) );
+       memset ( &mode->SubnetMask, 0, sizeof ( mode->SubnetMask ) );
+       memcpy ( &mode->SubnetMask, &netmask, sizeof ( netmask ) );
+
+       return 0;
+}
+
+/**
+ * Check if IP address matches filter
+ *
+ * @v pxe              PXE base code
+ * @v ip               EFI IP address
+ * @ret is_match       IP address matches filter
+ */
+static int efi_pxe_ip_filter ( struct efi_pxe *pxe, EFI_IP_ADDRESS *ip ) {
+       EFI_PXE_BASE_CODE_MODE *mode = &pxe->mode;
+       EFI_PXE_BASE_CODE_IP_FILTER *filter = &mode->IpFilter;
+       uint8_t filters = filter->Filters;
+       union {
+               EFI_IP_ADDRESS ip;
+               struct in_addr in;
+               struct in6_addr in6;
+       } *u = container_of ( ip, typeof ( *u ), ip );
+       size_t addr_len = pxe->net->net_addr_len;
+       unsigned int i;
+
+       /* Match everything, if applicable */
+       if ( filters & EFI_PXE_BASE_CODE_IP_FILTER_PROMISCUOUS )
+               return 1;
+
+       /* Match all multicasts, if applicable */
+       if ( filters & EFI_PXE_BASE_CODE_IP_FILTER_PROMISCUOUS_MULTICAST ) {
+               if ( mode->UsingIpv6 ) {
+                       if ( IN6_IS_ADDR_MULTICAST ( &u->in6 ) )
+                               return 1;
+               } else {
+                       if ( IN_IS_MULTICAST ( u->in.s_addr ) )
+                               return 1;
+               }
+       }
+
+       /* Match IPv4 broadcasts, if applicable */
+       if ( filters & EFI_PXE_BASE_CODE_IP_FILTER_BROADCAST ) {
+               if ( ( ! mode->UsingIpv6 ) &&
+                    ( u->in.s_addr == INADDR_BROADCAST ) )
+                       return 1;
+       }
+
+       /* Match station address, if applicable */
+       if ( filters & EFI_PXE_BASE_CODE_IP_FILTER_STATION_IP ) {
+               if ( memcmp ( ip, &mode->StationIp, addr_len ) == 0 )
+                       return 1;
+       }
+
+       /* Match explicit addresses, if applicable */
+       for ( i = 0 ; i < filter->IpCnt ; i++ ) {
+               if ( memcmp ( ip, &filter->IpList[i], addr_len ) == 0 )
+                       return 1;
+       }
+
+       return 0;
+}
+
+/******************************************************************************
+ *
+ * Data transfer buffer
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Reallocate PXE data transfer buffer
+ *
+ * @v xferbuf          Data transfer buffer
+ * @v len              New length (or zero to free buffer)
+ * @ret rc             Return status code
+ */
+static int efi_pxe_buf_realloc ( struct xfer_buffer *xferbuf __unused,
+                                size_t len __unused ) {
+
+       /* Can never reallocate: return EFI_BUFFER_TOO_SMALL */
+       return -ERANGE;
+}
+
+/**
+ * Write data to PXE data transfer buffer
+ *
+ * @v xferbuf          Data transfer buffer
+ * @v offset           Starting offset
+ * @v data             Data to copy
+ * @v len              Length of data
+ */
+static void efi_pxe_buf_write ( struct xfer_buffer *xferbuf, size_t offset,
+                               const void *data, size_t len ) {
+
+       /* Copy data to buffer */
+       memcpy ( ( xferbuf->data + offset ), data, len );
+}
+
+/** PXE data transfer buffer operations */
+static struct xfer_buffer_operations efi_pxe_buf_operations = {
+       .realloc = efi_pxe_buf_realloc,
+       .write = efi_pxe_buf_write,
+};
+
+/******************************************************************************
+ *
+ * (M)TFTP download interface
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Close PXE (M)TFTP download interface
+ *
+ * @v pxe              PXE base code
+ * @v rc               Reason for close
+ */
+static void efi_pxe_tftp_close ( struct efi_pxe *pxe, int rc ) {
+
+       /* Restart interface */
+       intf_restart ( &pxe->tftp, rc );
+
+       /* Record overall status */
+       pxe->rc = rc;
+}
+
+/**
+ * Check PXE (M)TFTP download flow control window
+ *
+ * @v pxe              PXE base code
+ * @ret len            Length of window
+ */
+static size_t efi_pxe_tftp_window ( struct efi_pxe *pxe ) {
+
+       /* Return requested blocksize */
+       return pxe->blksize;
+}
+
+/**
+ * Receive new PXE (M)TFTP download data
+ *
+ * @v pxe              PXE base code
+ * @v iobuf            I/O buffer
+ * @v meta             Transfer metadata
+ * @ret rc             Return status code
+ */
+static int efi_pxe_tftp_deliver ( struct efi_pxe *pxe,
+                                 struct io_buffer *iobuf,
+                                 struct xfer_metadata *meta ) {
+       int rc;
+
+       /* Deliver to data transfer buffer */
+       if ( ( rc = xferbuf_deliver ( &pxe->buf, iob_disown ( iobuf ),
+                                     meta ) ) != 0 )
+               goto err_deliver;
+
+       return 0;
+
+ err_deliver:
+       efi_pxe_tftp_close ( pxe, rc );
+       return rc;
+}
+
+/** PXE file data transfer interface operations */
+static struct interface_operation efi_pxe_tftp_operations[] = {
+       INTF_OP ( xfer_deliver, struct efi_pxe *, efi_pxe_tftp_deliver ),
+       INTF_OP ( xfer_window, struct efi_pxe *, efi_pxe_tftp_window ),
+       INTF_OP ( intf_close, struct efi_pxe *, efi_pxe_tftp_close ),
+};
+
+/** PXE file data transfer interface descriptor */
+static struct interface_descriptor efi_pxe_tftp_desc =
+       INTF_DESC ( struct efi_pxe, tftp, efi_pxe_tftp_operations );
+
+/**
+ * Open (M)TFTP download interface
+ *
+ * @v pxe              PXE base code
+ * @v ip               EFI IP address
+ * @v filename         Filename
+ * @ret rc             Return status code
+ */
+static int efi_pxe_tftp_open ( struct efi_pxe *pxe, EFI_IP_ADDRESS *ip,
+                              const char *filename ) {
+       struct sockaddr server;
+       struct uri *uri;
+       int rc;
+
+       /* Parse server address and filename */
+       efi_pxe_ip_sockaddr ( pxe, ip, &server );
+       uri = pxe_uri ( &server, filename );
+       if ( ! uri ) {
+               DBGC ( pxe, "PXE %s could not parse %s:%s\n", pxe->name,
+                      efi_pxe_ip_ntoa ( pxe, ip ), filename );
+               rc = -ENOTSUP;
+               goto err_parse;
+       }
+
+       /* Open URI */
+       if ( ( rc = xfer_open_uri ( &pxe->tftp, uri ) ) != 0 ) {
+               DBGC ( pxe, "PXE %s could not open: %s\n",
+                      pxe->name, strerror ( rc ) );
+               goto err_open;
+       }
+
+ err_open:
+       uri_put ( uri );
+ err_parse:
+       return rc;
+}
+
+/******************************************************************************
+ *
+ * UDP interface
+ *
+ ******************************************************************************
+ */
+
+/** EFI UDP pseudo-header */
+struct efi_pxe_udp_pseudo_header {
+       /** Network-layer protocol */
+       struct net_protocol *net;
+       /** Destination port */
+       uint16_t dest_port;
+       /** Source port */
+       uint16_t src_port;
+} __attribute__ (( packed ));
+
+/**
+ * Close UDP interface
+ *
+ * @v pxe              PXE base code
+ * @v rc               Reason for close
+ */
+static void efi_pxe_udp_close ( struct efi_pxe *pxe, int rc ) {
+       struct io_buffer *iobuf;
+       struct io_buffer *tmp;
+
+       /* Release our claim on SNP devices, if applicable */
+       if ( process_running ( &pxe->process ) )
+               efi_snp_release();
+
+       /* Stop process */
+       process_del ( &pxe->process );
+
+       /* Restart UDP interface */
+       intf_restart ( &pxe->udp, rc );
+
+       /* Flush any received UDP packets */
+       list_for_each_entry_safe ( iobuf, tmp, &pxe->queue, list ) {
+               list_del ( &iobuf->list );
+               free_iob ( iobuf );
+       }
+}
+
+/**
+ * Receive UDP packet
+ *
+ * @v pxe              PXE base code
+ * @v iobuf            I/O buffer
+ * @v meta             Data transfer metadata
+ * @ret rc             Return status code
+ */
+static int efi_pxe_udp_deliver ( struct efi_pxe *pxe, struct io_buffer *iobuf,
+                                struct xfer_metadata *meta ) {
+       struct sockaddr_efi *se_src;
+       struct sockaddr_efi *se_dest;
+       struct tcpip_net_protocol *tcpip;
+       struct net_protocol *net;
+       struct efi_pxe_udp_pseudo_header *pshdr;
+       size_t addr_len;
+       size_t pshdr_len;
+       int rc;
+
+       /* Sanity checks */
+       assert ( meta != NULL );
+       se_src = ( ( struct sockaddr_efi * ) meta->src );
+       assert ( se_src != NULL );
+       se_dest = ( ( struct sockaddr_efi * ) meta->dest );
+       assert ( se_dest != NULL );
+       assert ( se_src->se_family == se_dest->se_family );
+
+       /* Determine protocol */
+       tcpip = tcpip_net_protocol ( se_src->se_family );
+       if ( ! tcpip ) {
+               rc = -ENOTSUP;
+               goto err_unsupported;
+       }
+       net = tcpip->net_protocol;
+       addr_len = net->net_addr_len;
+
+       /* Construct pseudo-header */
+       pshdr_len = ( sizeof ( *pshdr ) + ( 2 * addr_len ) );
+       if ( ( rc = iob_ensure_headroom ( iobuf, pshdr_len ) ) != 0 )
+               goto err_headroom;
+       memcpy ( iob_push ( iobuf, addr_len ), &se_src->se_addr, addr_len );
+       memcpy ( iob_push ( iobuf, addr_len ), &se_dest->se_addr, addr_len );
+       pshdr = iob_push ( iobuf, sizeof ( *pshdr ) );
+       pshdr->net = net;
+       pshdr->dest_port = ntohs ( se_dest->se_port );
+       pshdr->src_port = ntohs ( se_src->se_port );
+
+       /* Add to queue */
+       list_add_tail ( &iobuf->list, &pxe->queue );
+
+       return 0;
+
+ err_unsupported:
+ err_headroom:
+       free_iob ( iobuf );
+       return rc;
+}
+
+/** PXE UDP interface operations */
+static struct interface_operation efi_pxe_udp_operations[] = {
+       INTF_OP ( xfer_deliver, struct efi_pxe *, efi_pxe_udp_deliver ),
+       INTF_OP ( intf_close, struct efi_pxe *, efi_pxe_udp_close ),
+};
+
+/** PXE UDP interface descriptor */
+static struct interface_descriptor efi_pxe_udp_desc =
+       INTF_DESC ( struct efi_pxe, udp, efi_pxe_udp_operations );
+
+/**
+ * Open UDP interface
+ *
+ * @v pxe              PXE base code
+ * @ret rc             Return status code
+ */
+static int efi_pxe_udp_open ( struct efi_pxe *pxe ) {
+       int rc;
+
+       /* If interface is already open, then cancel the scheduled close */
+       if ( process_running ( &pxe->process ) ) {
+               process_del ( &pxe->process );
+               return 0;
+       }
+
+       /* Open promiscuous UDP interface */
+       if ( ( rc = udp_open_promisc ( &pxe->udp ) ) != 0 ) {
+               DBGC ( pxe, "PXE %s could not open UDP connection: %s\n",
+                      pxe->name, strerror ( rc ) );
+               return rc;
+       }
+
+       /* Claim network devices */
+       efi_snp_claim();
+
+       return 0;
+}
+
+/**
+ * Schedule close of UDP interface
+ *
+ * @v pxe              PXE base code
+ */
+static void efi_pxe_udp_schedule_close ( struct efi_pxe *pxe ) {
+
+       /* The EFI PXE base code protocol does not provide any
+        * explicit UDP open/close methods.  To avoid the overhead of
+        * reopening a socket for each read/write operation, we start
+        * a process which will close the socket immediately if the
+        * next call into iPXE is anything other than a UDP
+        * read/write.
+        */
+       process_add ( &pxe->process );
+}
+
+/**
+ * Scheduled close of UDP interface
+ *
+ * @v pxe              PXE base code
+ */
+static void efi_pxe_udp_scheduled_close ( struct efi_pxe *pxe ) {
+
+       /* Close UDP interface */
+       efi_pxe_udp_close ( pxe, 0 );
+}
+
+/** UDP close process descriptor */
+static struct process_descriptor efi_pxe_process_desc =
+       PROC_DESC_ONCE ( struct efi_pxe, process, efi_pxe_udp_scheduled_close );
+
+/******************************************************************************
+ *
+ * Fake DHCP packets
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Name fake DHCP packet
+ *
+ * @v pxe              PXE base code
+ * @v packet           Packet
+ * @ret name           Name of packet
+ */
+static const char * efi_pxe_fake_name ( struct efi_pxe *pxe,
+                                       EFI_PXE_BASE_CODE_PACKET *packet ) {
+       EFI_PXE_BASE_CODE_MODE *mode = &pxe->mode;
+
+       if ( packet == &mode->DhcpDiscover ) {
+               return "DhcpDiscover";
+       } else if ( packet == &mode->DhcpAck ) {
+               return "DhcpAck";
+       } else if ( packet == &mode->ProxyOffer ) {
+               return "ProxyOffer";
+       } else if ( packet == &mode->PxeDiscover ) {
+               return "PxeDiscover";
+       } else if ( packet == &mode->PxeReply ) {
+               return "PxeReply";
+       } else if ( packet == &mode->PxeBisReply ) {
+               return "PxeBisReply";
+       } else {
+               return "<UNKNOWN>";
+       }
+}
+
+/**
+ * Construct fake DHCP packet and flag
+ *
+ * @v pxe              PXE base code
+ * @v fake             Fake packet constructor
+ * @v packet           Packet to fill in
+ * @ret exists         Packet existence flag
+ */
+static BOOLEAN efi_pxe_fake ( struct efi_pxe *pxe,
+                             int ( * fake ) ( struct net_device *netdev,
+                                              void *data, size_t len ),
+                             EFI_PXE_BASE_CODE_PACKET *packet ) {
+       EFI_PXE_BASE_CODE_MODE *mode = &pxe->mode;
+       struct dhcp_packet dhcppkt;
+       struct dhcphdr *dhcphdr;
+       unsigned int len;
+       int rc;
+
+       /* The fake packet constructors do not support IPv6 */
+       if ( mode->UsingIpv6 )
+               return FALSE;
+
+       /* Attempt to construct packet */
+       if ( ( rc = fake ( pxe->netdev, packet, sizeof ( *packet ) ) != 0 ) ) {
+               DBGC ( pxe, "PXE %s could not fake %s: %s\n", pxe->name,
+                      efi_pxe_fake_name ( pxe, packet ), strerror ( rc ) );
+               return FALSE;
+       }
+
+       /* The WDS bootstrap wdsmgfw.efi has a buggy DHCPv4 packet
+        * parser which does not correctly handle DHCP padding bytes.
+        * Specifically, if a padding byte (i.e. a zero) is
+        * encountered, the parse will first increment the pointer by
+        * one to skip over the padding byte but will then drop into
+        * the code path for handling normal options, which increments
+        * the pointer by two to skip over the (already-skipped) type
+        * field and the (non-existent) length field.
+        *
+        * The upshot of this bug in WDS is that the parser will fail
+        * with an error 0xc0000023 if the number of spare bytes after
+        * the end of the options is not an exact multiple of three.
+        *
+        * Work around this buggy parser by adding an explicit
+        * DHCP_END tag.
+        */
+       dhcphdr = container_of ( &packet->Dhcpv4.BootpOpcode,
+                                struct dhcphdr, op );
+       dhcppkt_init ( &dhcppkt, dhcphdr, sizeof ( *packet ) );
+       len = dhcppkt_len ( &dhcppkt );
+       if ( len < sizeof ( *packet ) )
+               packet->Raw[len] = DHCP_END;
+
+       return TRUE;
+}
+
+/**
+ * Construct fake DHCP packets
+ *
+ * @v pxe              PXE base code
+ */
+static void efi_pxe_fake_all ( struct efi_pxe *pxe ) {
+       EFI_PXE_BASE_CODE_MODE *mode = &pxe->mode;
+
+       /* Construct fake packets */
+       mode->DhcpDiscoverValid =
+               efi_pxe_fake ( pxe, create_fakedhcpdiscover,
+                              &mode->DhcpDiscover );
+       mode->DhcpAckReceived =
+               efi_pxe_fake ( pxe, create_fakedhcpack,
+                              &mode->DhcpAck );
+       mode->PxeReplyReceived =
+               efi_pxe_fake ( pxe, create_fakepxebsack,
+                              &mode->PxeReply );
+}
+
+/******************************************************************************
+ *
+ * Base code protocol
+ *
+ ******************************************************************************
+ */
+
+/**
+ * Start PXE base code
+ *
+ * @v base             PXE base code protocol
+ * @v use_ipv6         Use IPv6
+ * @ret efirc          EFI status code
+ */
+static EFI_STATUS EFIAPI efi_pxe_start ( EFI_PXE_BASE_CODE_PROTOCOL *base,
+                                        BOOLEAN use_ipv6 ) {
+       struct efi_pxe *pxe = container_of ( base, struct efi_pxe, base );
+       EFI_PXE_BASE_CODE_MODE *mode = &pxe->mode;
+       struct tcpip_net_protocol *ipv6 = tcpip_net_protocol ( AF_INET6 );
+       sa_family_t family = ( use_ipv6 ? AF_INET6 : AF_INET );
+       int rc;
+
+       DBGC ( pxe, "PXE %s START %s\n", pxe->name, ( ipv6 ? "IPv6" : "IPv4" ));
+
+       /* Initialise mode structure */
+       memset ( mode, 0, sizeof ( *mode ) );
+       mode->AutoArp = TRUE;
+       mode->TTL = DEFAULT_TTL;
+       mode->ToS = DEFAULT_ToS;
+       mode->IpFilter.Filters =
+               ( EFI_PXE_BASE_CODE_IP_FILTER_STATION_IP |
+                 EFI_PXE_BASE_CODE_IP_FILTER_BROADCAST |
+                 EFI_PXE_BASE_CODE_IP_FILTER_PROMISCUOUS |
+                 EFI_PXE_BASE_CODE_IP_FILTER_PROMISCUOUS_MULTICAST );
+
+       /* Check for IPv4/IPv6 support */
+       mode->Ipv6Supported = ( ipv6 != NULL );
+       mode->Ipv6Available = ( ipv6 != NULL );
+       pxe->tcpip = tcpip_net_protocol ( family );
+       if ( ! pxe->tcpip ) {
+               DBGC ( pxe, "PXE %s has no support for %s\n",
+                      pxe->name, socket_family_name ( family ) );
+               return EFI_UNSUPPORTED;
+       }
+       pxe->net = pxe->tcpip->net_protocol;
+       mode->UsingIpv6 = use_ipv6;
+
+       /* Populate station IP address */
+       if ( ( rc = efi_pxe_ip ( pxe ) ) != 0 )
+               return rc;
+
+       /* Construct fake DHCP packets */
+       efi_pxe_fake_all ( pxe );
+
+       /* Record that base code is started */
+       mode->Started = TRUE;
+       DBGC ( pxe, "PXE %s using %s\n",
+              pxe->name, pxe->net->ntoa ( &mode->StationIp ) );
+
+       return 0;
+}
+
+/**
+ * Stop PXE base code
+ *
+ * @v base             PXE base code protocol
+ * @ret efirc          EFI status code
+ */
+static EFI_STATUS EFIAPI efi_pxe_stop ( EFI_PXE_BASE_CODE_PROTOCOL *base ) {
+       struct efi_pxe *pxe = container_of ( base, struct efi_pxe, base );
+       EFI_PXE_BASE_CODE_MODE *mode = &pxe->mode;
+
+       DBGC ( pxe, "PXE %s STOP\n", pxe->name );
+
+       /* Record that base code is stopped */
+       mode->Started = FALSE;
+
+       /* Close TFTP */
+       efi_pxe_tftp_close ( pxe, 0 );
+
+       /* Close UDP */
+       efi_pxe_udp_close ( pxe, 0 );
+
+       return 0;
+}
+
+/**
+ * Perform DHCP
+ *
+ * @v base             PXE base code protocol
+ * @v sort             Offers should be sorted
+ * @ret efirc          EFI status code
+ */
+static EFI_STATUS EFIAPI efi_pxe_dhcp ( EFI_PXE_BASE_CODE_PROTOCOL *base,
+                                       BOOLEAN sort ) {
+       struct efi_pxe *pxe = container_of ( base, struct efi_pxe, base );
+       struct net_device *netdev = pxe->netdev;
+       int rc;
+
+       DBGC ( pxe, "PXE %s DHCP %s\n",
+              pxe->name, ( sort ? "sorted" : "unsorted" ) );
+
+       /* Claim network devices */
+       efi_snp_claim();
+
+       /* Initiate configuration */
+       if ( ( rc = netdev_configure_all ( netdev ) ) != 0 ) {
+               DBGC ( pxe, "PXE %s could not initiate configuration: %s\n",
+                      pxe->name, strerror ( rc ) );
+               goto err_configure;
+       }
+
+       /* Wait for configuration to complete (or time out) */
+       while ( netdev_configuration_in_progress ( netdev ) )
+               step();
+
+       /* Report timeout if configuration failed */
+       if ( ! netdev_configuration_ok ( netdev ) ) {
+               rc = -ETIMEDOUT;
+               goto err_timeout;
+       }
+
+       /* Update station IP address */
+       if ( ( rc = efi_pxe_ip ( pxe ) ) != 0 )
+               goto err_ip;
+
+       /* Update faked DHCP packets */
+       efi_pxe_fake_all ( pxe );
+
+ err_ip:
+ err_timeout:
+ err_configure:
+       efi_snp_release();
+       return EFIRC ( rc );
+}
+
+/**
+ * Perform boot server discovery
+ *
+ * @v base             PXE base code protocol
+ * @v type             Boot server type
+ * @v layer            Boot server layer
+ * @v bis              Use boot integrity services
+ * @v info             Additional information
+ * @ret efirc          EFI status code
+ */
+static EFI_STATUS EFIAPI
+efi_pxe_discover ( EFI_PXE_BASE_CODE_PROTOCOL *base, UINT16 type, UINT16 *layer,
+                  BOOLEAN bis, EFI_PXE_BASE_CODE_DISCOVER_INFO *info ) {
+       struct efi_pxe *pxe = container_of ( base, struct efi_pxe, base );
+       EFI_IP_ADDRESS *ip;
+       unsigned int i;
+
+       DBGC ( pxe, "PXE %s DISCOVER type %d layer %d%s\n",
+              pxe->name, type, *layer, ( bis ? " bis" : "" ) );
+       if ( info ) {
+               DBGC ( pxe, "%s%s%s%s %s",
+                      ( info->UseMCast ? " mcast" : "" ),
+                      ( info->UseBCast ? " bcast" : "" ),
+                      ( info->UseUCast ? " ucast" : "" ),
+                      ( info->MustUseList ? " list" : "" ),
+                      efi_pxe_ip_ntoa ( pxe, &info->ServerMCastIp ) );
+               for ( i = 0 ; i < info->IpCnt ; i++ ) {
+                       ip = &info->SrvList[i].IpAddr;
+                       DBGC ( pxe, " %d%s:%s", info->SrvList[i].Type,
+                              ( info->SrvList[i].AcceptAnyResponse ?
+                                ":any" : "" ), efi_pxe_ip_ntoa ( pxe, ip ) );
+               }
+       }
+       DBGC ( pxe, "\n" );
+
+       /* Not used by any bootstrap I can find to test with */
+       return EFI_UNSUPPORTED;
+}
+
+/**
+ * Perform (M)TFTP
+ *
+ * @v base             PXE base code protocol
+ * @v opcode           TFTP opcode
+ * @v data             Data buffer
+ * @v overwrite                Overwrite file
+ * @v len              Length of data buffer
+ * @v blksize          Block size
+ * @v ip               Server address
+ * @v filename         Filename
+ * @v info             Additional information
+ * @v callback         Pass packets to callback instead of data buffer
+ * @ret efirc          EFI status code
+ */
+static EFI_STATUS EFIAPI
+efi_pxe_mtftp ( EFI_PXE_BASE_CODE_PROTOCOL *base,
+               EFI_PXE_BASE_CODE_TFTP_OPCODE opcode, VOID *data,
+               BOOLEAN overwrite, UINT64 *len, UINTN *blksize,
+               EFI_IP_ADDRESS *ip, UINT8 *filename,
+               EFI_PXE_BASE_CODE_MTFTP_INFO *info, BOOLEAN callback ) {
+       struct efi_pxe *pxe = container_of ( base, struct efi_pxe, base );
+       int rc;
+
+       DBGC ( pxe, "PXE %s MTFTP %d%s %p+%llx", pxe->name, opcode,
+              ( overwrite ? " overwrite" : "" ), data, *len );
+       if ( blksize )
+               DBGC ( pxe, " blksize %zd", ( ( size_t ) *blksize ) );
+       DBGC ( pxe, " %s:%s", efi_pxe_ip_ntoa ( pxe, ip ), filename );
+       if ( info ) {
+               DBGC ( pxe, " %s:%d:%d:%d:%d",
+                      efi_pxe_ip_ntoa ( pxe, &info->MCastIp ),
+                      info->CPort, info->SPort, info->ListenTimeout,
+                      info->TransmitTimeout );
+       }
+       DBGC ( pxe, "%s\n", ( callback ? " callback" : "" ) );
+
+       /* Fail unless operation is supported */
+       if ( ! ( ( opcode == EFI_PXE_BASE_CODE_TFTP_READ_FILE ) ||
+                ( opcode == EFI_PXE_BASE_CODE_MTFTP_READ_FILE ) ) ) {
+               DBGC ( pxe, "PXE %s unsupported MTFTP opcode %d\n",
+                      pxe->name, opcode );
+               rc = -ENOTSUP;
+               goto err_opcode;
+       }
+
+       /* Claim network devices */
+       efi_snp_claim();
+
+       /* Determine block size.  Ignore the requested block size
+        * unless we are using callbacks, since limiting HTTP to a
+        * 512-byte TCP window is not sensible.
+        */
+       pxe->blksize = ( ( callback && blksize ) ? *blksize : -1UL );
+
+       /* Initialise data transfer buffer */
+       pxe->buf.data = data;
+       pxe->buf.len = *len;
+
+       /* Open download */
+       if ( ( rc = efi_pxe_tftp_open ( pxe, ip,
+                                       ( ( const char * ) filename ) ) ) != 0 )
+               goto err_open;
+
+       /* Wait for download to complete */
+       pxe->rc = -EINPROGRESS;
+       while ( pxe->rc == -EINPROGRESS )
+               step();
+       if ( ( rc = pxe->rc ) != 0 ) {
+               DBGC ( pxe, "PXE %s download failed: %s\n",
+                      pxe->name, strerror ( rc ) );
+               goto err_download;
+       }
+
+ err_download:
+       efi_pxe_tftp_close ( pxe, rc );
+ err_open:
+       efi_snp_release();
+ err_opcode:
+       return EFIRC ( rc );
+}
+
+/**
+ * Transmit UDP packet
+ *
+ * @v base             PXE base code protocol
+ * @v flags            Operation flags
+ * @v dest_ip          Destination address
+ * @v dest_port                Destination port
+ * @v gateway          Gateway address
+ * @v src_ip           Source address
+ * @v src_port         Source port
+ * @v hdr_len          Header length
+ * @v hdr              Header data
+ * @v len              Length
+ * @v data             Data
+ * @ret efirc          EFI status code
+ */
+static EFI_STATUS EFIAPI
+efi_pxe_udp_write ( EFI_PXE_BASE_CODE_PROTOCOL *base, UINT16 flags,
+                   EFI_IP_ADDRESS *dest_ip,
+                   EFI_PXE_BASE_CODE_UDP_PORT *dest_port,
+                   EFI_IP_ADDRESS *gateway, EFI_IP_ADDRESS *src_ip,
+                   EFI_PXE_BASE_CODE_UDP_PORT *src_port,
+                   UINTN *hdr_len, VOID *hdr, UINTN *len, VOID *data ) {
+       struct efi_pxe *pxe = container_of ( base, struct efi_pxe, base );
+       EFI_PXE_BASE_CODE_MODE *mode = &pxe->mode;
+       struct io_buffer *iobuf;
+       struct xfer_metadata meta;
+       union {
+               struct sockaddr_tcpip st;
+               struct sockaddr sa;
+       } dest;
+       union {
+               struct sockaddr_tcpip st;
+               struct sockaddr sa;
+       } src;
+       int rc;
+
+       DBGC2 ( pxe, "PXE %s UDP WRITE ", pxe->name );
+       if ( src_ip )
+               DBGC2 ( pxe, "%s", efi_pxe_ip_ntoa ( pxe, src_ip ) );
+       DBGC2 ( pxe, ":" );
+       if ( src_port &&
+            ( ! ( flags & EFI_PXE_BASE_CODE_UDP_OPFLAGS_ANY_SRC_PORT ) ) ) {
+               DBGC2 ( pxe, "%d", *src_port );
+       } else {
+               DBGC2 ( pxe, "*" );
+       }
+       DBGC2 ( pxe, "->%s:%d", efi_pxe_ip_ntoa ( pxe, dest_ip ), *dest_port );
+       if ( gateway )
+               DBGC2 ( pxe, " via %s", efi_pxe_ip_ntoa ( pxe, gateway ) );
+       if ( hdr_len )
+               DBGC2 ( pxe, " %p+%zx", hdr, ( ( size_t ) *hdr_len ) );
+       DBGC2 ( pxe, " %p+%zx", data, ( ( size_t ) *len ) );
+       if ( flags & EFI_PXE_BASE_CODE_UDP_OPFLAGS_MAY_FRAGMENT )
+               DBGC2 ( pxe, " frag" );
+       DBGC2 ( pxe, "\n" );
+
+       /* Open UDP connection (if applicable) */
+       if ( ( rc = efi_pxe_udp_open ( pxe ) ) != 0 )
+               goto err_open;
+
+       /* Construct destination address */
+       efi_pxe_ip_sockaddr ( pxe, dest_ip, &dest.sa );
+       dest.st.st_port = htons ( *dest_port );
+
+       /* Construct source address */
+       efi_pxe_ip_sockaddr ( pxe, ( src_ip ? src_ip : &mode->StationIp ),
+                             &src.sa );
+       if ( src_port &&
+            ( ! ( flags & EFI_PXE_BASE_CODE_UDP_OPFLAGS_ANY_SRC_PORT ) ) ) {
+               src.st.st_port = htons ( *src_port );
+       } else {
+               /* The API does not allow for a sensible concept of
+                * binding to a local port, so just use a random value.
+                */
+               src.st.st_port = ( random() | htons ( 1024 ) );
+               if ( src_port )
+                       *src_port = ntohs ( src.st.st_port );
+       }
+
+       /* Allocate I/O buffer */
+       iobuf = xfer_alloc_iob ( &pxe->udp,
+                                ( *len + ( hdr_len ? *hdr_len : 0 ) ) );
+       if ( ! iobuf ) {
+               rc = -ENOMEM;
+               goto err_alloc;
+       }
+
+       /* Populate I/O buffer */
+       if ( hdr_len )
+               memcpy ( iob_put ( iobuf, *hdr_len ), hdr, *hdr_len );
+       memcpy ( iob_put ( iobuf, *len ), data, *len );
+
+       /* Construct metadata */
+       memset ( &meta, 0, sizeof ( meta ) );
+       meta.src = &src.sa;
+       meta.dest = &dest.sa;
+       meta.netdev = pxe->netdev;
+
+       /* Deliver I/O buffer */
+       if ( ( rc = xfer_deliver ( &pxe->udp, iob_disown ( iobuf ),
+                                  &meta ) ) != 0 ) {
+               DBGC ( pxe, "PXE %s could not transmit: %s\n",
+                      pxe->name, strerror ( rc ) );
+               goto err_deliver;
+       }
+
+ err_deliver:
+       free_iob ( iobuf );
+ err_alloc:
+       efi_pxe_udp_schedule_close ( pxe );
+ err_open:
+       return EFIRC ( rc );
+}
+
+/**
+ * Receive UDP packet
+ *
+ * @v base             PXE base code protocol
+ * @v flags            Operation flags
+ * @v dest_ip          Destination address
+ * @v dest_port                Destination port
+ * @v src_ip           Source address
+ * @v src_port         Source port
+ * @v hdr_len          Header length
+ * @v hdr              Header data
+ * @v len              Length
+ * @v data             Data
+ * @ret efirc          EFI status code
+ */
+static EFI_STATUS EFIAPI
+efi_pxe_udp_read ( EFI_PXE_BASE_CODE_PROTOCOL *base, UINT16 flags,
+                  EFI_IP_ADDRESS *dest_ip,
+                  EFI_PXE_BASE_CODE_UDP_PORT *dest_port,
+                  EFI_IP_ADDRESS *src_ip,
+                  EFI_PXE_BASE_CODE_UDP_PORT *src_port,
+                  UINTN *hdr_len, VOID *hdr, UINTN *len, VOID *data ) {
+       struct efi_pxe *pxe = container_of ( base, struct efi_pxe, base );
+       struct io_buffer *iobuf;
+       struct efi_pxe_udp_pseudo_header *pshdr;
+       EFI_IP_ADDRESS *actual_dest_ip;
+       EFI_IP_ADDRESS *actual_src_ip;
+       size_t addr_len;
+       size_t frag_len;
+       int rc;
+
+       DBGC2 ( pxe, "PXE %s UDP READ ", pxe->name );
+       if ( flags & EFI_PXE_BASE_CODE_UDP_OPFLAGS_USE_FILTER ) {
+               DBGC2 ( pxe, "(filter)" );
+       } else if ( flags & EFI_PXE_BASE_CODE_UDP_OPFLAGS_ANY_DEST_IP ) {
+               DBGC2 ( pxe, "*" );
+       } else if ( dest_ip ) {
+               DBGC2 ( pxe, "%s", efi_pxe_ip_ntoa ( pxe, dest_ip ) );
+       }
+       DBGC2 ( pxe, ":" );
+       if ( flags & EFI_PXE_BASE_CODE_UDP_OPFLAGS_ANY_DEST_PORT ) {
+               DBGC2 ( pxe, "*" );
+       } else if ( dest_port ) {
+               DBGC2 ( pxe, "%d", *dest_port );
+       } else {
+               DBGC2 ( pxe, "<NULL>" );
+       }
+       DBGC2 ( pxe, "<-" );
+       if ( flags & EFI_PXE_BASE_CODE_UDP_OPFLAGS_ANY_SRC_IP ) {
+               DBGC2 ( pxe, "*" );
+       } else if ( src_ip ) {
+               DBGC2 ( pxe, "%s", efi_pxe_ip_ntoa ( pxe, src_ip ) );
+       } else {
+               DBGC2 ( pxe, "<NULL>" );
+       }
+       DBGC2 ( pxe, ":" );
+       if ( flags & EFI_PXE_BASE_CODE_UDP_OPFLAGS_ANY_SRC_PORT ) {
+               DBGC2 ( pxe, "*" );
+       } else if ( src_port ) {
+               DBGC2 ( pxe, "%d", *src_port );
+       } else {
+               DBGC2 ( pxe, "<NULL>" );
+       }
+       if ( hdr_len )
+               DBGC2 ( pxe, " %p+%zx", hdr, ( ( size_t ) *hdr_len ) );
+       DBGC2 ( pxe, " %p+%zx\n", data, ( ( size_t ) *len ) );
+
+       /* Open UDP connection (if applicable) */
+       if ( ( rc = efi_pxe_udp_open ( pxe ) ) != 0 )
+               goto err_open;
+
+       /* Try receiving a packet, if the queue is empty */
+       if ( list_empty ( &pxe->queue ) )
+               step();
+
+       /* Remove first packet from the queue */
+       iobuf = list_first_entry ( &pxe->queue, struct io_buffer, list );
+       if ( ! iobuf ) {
+               rc = -ETIMEDOUT; /* "no packet" */
+               goto err_empty;
+       }
+       list_del ( &iobuf->list );
+
+       /* Strip pseudo-header */
+       pshdr = iobuf->data;
+       addr_len = ( pshdr->net->net_addr_len );
+       iob_pull ( iobuf, sizeof ( *pshdr ) );
+       actual_dest_ip = iobuf->data;
+       iob_pull ( iobuf, addr_len );
+       actual_src_ip = iobuf->data;
+       iob_pull ( iobuf, addr_len );
+       DBGC2 ( pxe, "PXE %s UDP RX %s:%d", pxe->name,
+               pshdr->net->ntoa ( actual_dest_ip ), pshdr->dest_port );
+       DBGC2 ( pxe, "<-%s:%d len %#zx\n", pshdr->net->ntoa ( actual_src_ip ),
+               pshdr->src_port, iob_len ( iobuf ) );
+
+       /* Filter based on network-layer protocol */
+       if ( pshdr->net != pxe->net ) {
+               DBGC2 ( pxe, "PXE %s filtered out %s packet\n",
+                       pxe->name, pshdr->net->name );
+               rc = -ETIMEDOUT; /* "no packet" */
+               goto err_filter;
+       }
+
+       /* Filter based on port numbers */
+       if ( ! ( ( flags & EFI_PXE_BASE_CODE_UDP_OPFLAGS_ANY_DEST_PORT ) ||
+                ( dest_port && ( *dest_port == pshdr->dest_port ) ) ) ) {
+               DBGC2 ( pxe, "PXE %s filtered out destination port %d\n",
+                       pxe->name, pshdr->dest_port );
+               rc = -ETIMEDOUT; /* "no packet" */
+               goto err_filter;
+       }
+       if ( ! ( ( flags & EFI_PXE_BASE_CODE_UDP_OPFLAGS_ANY_SRC_PORT ) ||
+                ( src_port && ( *src_port == pshdr->src_port ) ) ) ) {
+               DBGC2 ( pxe, "PXE %s filtered out source port %d\n",
+                       pxe->name, pshdr->src_port );
+               rc = -ETIMEDOUT; /* "no packet" */
+               goto err_filter;
+       }
+
+       /* Filter based on source IP address */
+       if ( ! ( ( flags & EFI_PXE_BASE_CODE_UDP_OPFLAGS_ANY_SRC_IP ) ||
+                ( src_ip &&
+                  ( memcmp ( src_ip, actual_src_ip, addr_len ) == 0 ) ) ) ) {
+               DBGC2 ( pxe, "PXE %s filtered out source IP %s\n",
+                       pxe->name, pshdr->net->ntoa ( actual_src_ip ) );
+               rc = -ETIMEDOUT; /* "no packet" */
+               goto err_filter;
+       }
+
+       /* Filter based on destination IP address */
+       if ( ! ( ( ( flags & EFI_PXE_BASE_CODE_UDP_OPFLAGS_USE_FILTER ) &&
+                  efi_pxe_ip_filter ( pxe, actual_dest_ip ) ) ||
+                ( ( ! ( flags & EFI_PXE_BASE_CODE_UDP_OPFLAGS_USE_FILTER ) ) &&
+                  ( ( flags & EFI_PXE_BASE_CODE_UDP_OPFLAGS_ANY_DEST_IP ) ||
+                       ( dest_ip && ( memcmp ( dest_ip, actual_dest_ip,
+                                               addr_len ) == 0 ) ) ) ) ) ) {
+               DBGC2 ( pxe, "PXE %s filtered out destination IP %s\n",
+                       pxe->name, pshdr->net->ntoa ( actual_dest_ip ) );
+               rc = -ETIMEDOUT; /* "no packet" */
+               goto err_filter;
+       }
+
+       /* Fill in addresses and port numbers */
+       if ( dest_ip )
+               memcpy ( dest_ip, actual_dest_ip, addr_len );
+       if ( dest_port )
+               *dest_port = pshdr->dest_port;
+       if ( src_ip )
+               memcpy ( src_ip, actual_src_ip, addr_len );
+       if ( src_port )
+               *src_port = pshdr->src_port;
+
+       /* Fill in header, if applicable */
+       if ( hdr_len ) {
+               frag_len = iob_len ( iobuf );
+               if ( frag_len > *hdr_len )
+                       frag_len = *hdr_len;
+               memcpy ( hdr, iobuf->data, frag_len );
+               iob_pull ( iobuf, frag_len );
+               *hdr_len = frag_len;
+       }
+
+       /* Fill in data buffer */
+       frag_len = iob_len ( iobuf );
+       if ( frag_len > *len )
+               frag_len = *len;
+       memcpy ( data, iobuf->data, frag_len );
+       iob_pull ( iobuf, frag_len );
+       *len = frag_len;
+
+       /* Check for overflow */
+       if ( iob_len ( iobuf ) ) {
+               rc = -ERANGE;
+               goto err_too_short;
+       }
+
+       /* Success */
+       rc = 0;
+
+ err_too_short:
+ err_filter:
+       free_iob ( iobuf );
+ err_empty:
+       efi_pxe_udp_schedule_close ( pxe );
+ err_open:
+       return EFIRC ( rc );
+}
+
+/**
+ * Set receive filter
+ *
+ * @v base             PXE base code protocol
+ * @v filter           Receive filter
+ * @ret efirc          EFI status code
+ */
+static EFI_STATUS EFIAPI
+efi_pxe_set_ip_filter ( EFI_PXE_BASE_CODE_PROTOCOL *base,
+                       EFI_PXE_BASE_CODE_IP_FILTER *filter ) {
+       struct efi_pxe *pxe = container_of ( base, struct efi_pxe, base );
+       EFI_PXE_BASE_CODE_MODE *mode = &pxe->mode;
+       unsigned int i;
+
+       DBGC ( pxe, "PXE %s SET IP FILTER %02x",
+              pxe->name, filter->Filters );
+       for ( i = 0 ; i < filter->IpCnt ; i++ ) {
+               DBGC ( pxe, " %s",
+                      efi_pxe_ip_ntoa ( pxe, &filter->IpList[i] ) );
+       }
+       DBGC ( pxe, "\n" );
+
+       /* Update filter */
+       memcpy ( &mode->IpFilter, filter, sizeof ( mode->IpFilter ) );
+
+       return 0;
+}
+
+/**
+ * Resolve MAC address
+ *
+ * @v base             PXE base code protocol
+ * @v ip               IP address
+ * @v mac              MAC address to fill in
+ * @ret efirc          EFI status code
+ */
+static EFI_STATUS EFIAPI efi_pxe_arp ( EFI_PXE_BASE_CODE_PROTOCOL *base,
+                                      EFI_IP_ADDRESS *ip,
+                                      EFI_MAC_ADDRESS *mac ) {
+       struct efi_pxe *pxe = container_of ( base, struct efi_pxe, base );
+
+       DBGC ( pxe, "PXE %s ARP %s %p\n",
+              pxe->name, efi_pxe_ip_ntoa ( pxe, ip ), mac );
+
+       /* Not used by any bootstrap I can find to test with */
+       return EFI_UNSUPPORTED;
+}
+
+/**
+ * Set parameters
+ *
+ * @v base             PXE base code protocol
+ * @v autoarp          Automatic ARP packet generation
+ * @v sendguid         Send GUID as client hardware address
+ * @v ttl              IP time to live
+ * @v tos              IP type of service
+ * @v callback         Make callbacks
+ * @ret efirc          EFI status code
+ */
+static EFI_STATUS EFIAPI
+efi_pxe_set_parameters ( EFI_PXE_BASE_CODE_PROTOCOL *base,
+                        BOOLEAN *autoarp, BOOLEAN *sendguid, UINT8 *ttl,
+                        UINT8 *tos, BOOLEAN *callback ) {
+       struct efi_pxe *pxe = container_of ( base, struct efi_pxe, base );
+       EFI_PXE_BASE_CODE_MODE *mode = &pxe->mode;
+
+       DBGC ( pxe, "PXE %s SET PARAMETERS", pxe->name );
+       if ( autoarp )
+               DBGC ( pxe, " %s", ( *autoarp ? "autoarp" : "noautoarp" ) );
+       if ( sendguid )
+               DBGC ( pxe, " %s", ( *sendguid ? "sendguid" : "sendmac" ) );
+       if ( ttl )
+               DBGC ( pxe, " ttl %d", *ttl );
+       if ( tos )
+               DBGC ( pxe, " tos %d", *tos );
+       if ( callback ) {
+               DBGC ( pxe, " %s",
+                      ( *callback ? "callback" : "nocallback" ) );
+       }
+       DBGC ( pxe, "\n" );
+
+       /* Update parameters */
+       if ( autoarp )
+               mode->AutoArp = *autoarp;
+       if ( sendguid )
+               mode->SendGUID = *sendguid;
+       if ( ttl )
+               mode->TTL = *ttl;
+       if ( tos )
+               mode->ToS = *tos;
+       if ( callback )
+               mode->MakeCallbacks = *callback;
+
+       return 0;
+}
+
+/**
+ * Set IP address
+ *
+ * @v base             PXE base code protocol
+ * @v ip               IP address
+ * @v netmask          Subnet mask
+ * @ret efirc          EFI status code
+ */
+static EFI_STATUS EFIAPI
+efi_pxe_set_station_ip ( EFI_PXE_BASE_CODE_PROTOCOL *base,
+                        EFI_IP_ADDRESS *ip, EFI_IP_ADDRESS *netmask ) {
+       struct efi_pxe *pxe = container_of ( base, struct efi_pxe, base );
+       EFI_PXE_BASE_CODE_MODE *mode = &pxe->mode;
+
+       DBGC ( pxe, "PXE %s SET STATION IP ", pxe->name );
+       if ( ip )
+               DBGC ( pxe, "%s", efi_pxe_ip_ntoa ( pxe, ip ) );
+       if ( netmask )
+               DBGC ( pxe, "/%s", efi_pxe_ip_ntoa ( pxe, netmask ) );
+       DBGC ( pxe, "\n" );
+
+       /* Update IP address and netmask */
+       if ( ip )
+               memcpy ( &mode->StationIp, ip, sizeof ( mode->StationIp ) );
+       if ( netmask )
+               memcpy ( &mode->SubnetMask, netmask, sizeof (mode->SubnetMask));
+
+       return 0;
+}
+
+/**
+ * Update cached DHCP packets
+ *
+ * @v base             PXE base code protocol
+ * @v dhcpdisc_ok      DHCPDISCOVER is valid
+ * @v dhcpack_ok       DHCPACK received
+ * @v proxyoffer_ok    ProxyDHCPOFFER received
+ * @v pxebsdisc_ok     PxeBsDISCOVER valid
+ * @v pxebsack_ok      PxeBsACK received
+ * @v pxebsbis_ok      PxeBsBIS received
+ * @v dhcpdisc         DHCPDISCOVER packet
+ * @v dhcpack          DHCPACK packet
+ * @v proxyoffer       ProxyDHCPOFFER packet
+ * @v pxebsdisc                PxeBsDISCOVER packet
+ * @v pxebsack         PxeBsACK packet
+ * @v pxebsbis         PxeBsBIS packet
+ * @ret efirc          EFI status code
+ */
+static EFI_STATUS EFIAPI
+efi_pxe_set_packets ( EFI_PXE_BASE_CODE_PROTOCOL *base, BOOLEAN *dhcpdisc_ok,
+                     BOOLEAN *dhcpack_ok, BOOLEAN *proxyoffer_ok,
+                     BOOLEAN *pxebsdisc_ok, BOOLEAN *pxebsack_ok,
+                     BOOLEAN *pxebsbis_ok, EFI_PXE_BASE_CODE_PACKET *dhcpdisc,
+                     EFI_PXE_BASE_CODE_PACKET *dhcpack,
+                     EFI_PXE_BASE_CODE_PACKET *proxyoffer,
+                     EFI_PXE_BASE_CODE_PACKET *pxebsdisc,
+                     EFI_PXE_BASE_CODE_PACKET *pxebsack,
+                     EFI_PXE_BASE_CODE_PACKET *pxebsbis ) {
+       struct efi_pxe *pxe = container_of ( base, struct efi_pxe, base );
+       EFI_PXE_BASE_CODE_MODE *mode = &pxe->mode;
+
+       DBGC ( pxe, "PXE %s SET PACKETS\n", pxe->name );
+
+       /* Update fake packet flags */
+       if ( dhcpdisc_ok )
+               mode->DhcpDiscoverValid = *dhcpdisc_ok;
+       if ( dhcpack_ok )
+               mode->DhcpAckReceived = *dhcpack_ok;
+       if ( proxyoffer_ok )
+               mode->ProxyOfferReceived = *proxyoffer_ok;
+       if ( pxebsdisc_ok )
+               mode->PxeDiscoverValid = *pxebsdisc_ok;
+       if ( pxebsack_ok )
+               mode->PxeReplyReceived = *pxebsack_ok;
+       if ( pxebsbis_ok )
+               mode->PxeBisReplyReceived = *pxebsbis_ok;
+
+       /* Update fake packet contents */
+       if ( dhcpdisc )
+               memcpy ( &mode->DhcpDiscover, dhcpdisc, sizeof ( *dhcpdisc ) );
+       if ( dhcpack )
+               memcpy ( &mode->DhcpAck, dhcpack, sizeof ( *dhcpack ) );
+       if ( proxyoffer )
+               memcpy ( &mode->ProxyOffer, proxyoffer, sizeof ( *proxyoffer ));
+       if ( pxebsdisc )
+               memcpy ( &mode->PxeDiscover, pxebsdisc, sizeof ( *pxebsdisc ) );
+       if ( pxebsack )
+               memcpy ( &mode->PxeReply, pxebsack, sizeof ( *pxebsack ) );
+       if ( pxebsbis )
+               memcpy ( &mode->PxeBisReply, pxebsbis, sizeof ( *pxebsbis ) );
+
+       return 0;
+}
+
+/** PXE base code protocol */
+static EFI_PXE_BASE_CODE_PROTOCOL efi_pxe_base_code_protocol = {
+       .Revision       = EFI_PXE_BASE_CODE_PROTOCOL_REVISION,
+       .Start          = efi_pxe_start,
+       .Stop           = efi_pxe_stop,
+       .Dhcp           = efi_pxe_dhcp,
+       .Discover       = efi_pxe_discover,
+       .Mtftp          = efi_pxe_mtftp,
+       .UdpWrite       = efi_pxe_udp_write,
+       .UdpRead        = efi_pxe_udp_read,
+       .SetIpFilter    = efi_pxe_set_ip_filter,
+       .Arp            = efi_pxe_arp,
+       .SetParameters  = efi_pxe_set_parameters,
+       .SetStationIp   = efi_pxe_set_station_ip,
+       .SetPackets     = efi_pxe_set_packets,
+};
+
+/**
+ * Install PXE base code protocol
+ *
+ * @v handle           EFI handle
+ * @v netdev           Underlying network device
+ * @ret rc             Return status code
+ */
+int efi_pxe_install ( EFI_HANDLE handle, struct net_device *netdev ) {
+       EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+       struct tcpip_net_protocol *ipv6 = tcpip_net_protocol ( AF_INET6 );
+       struct efi_pxe *pxe;
+       struct in_addr ip;
+       BOOLEAN use_ipv6;
+       EFI_STATUS efirc;
+       int rc;
+
+       /* Allocate and initialise structure */
+       pxe = zalloc ( sizeof ( *pxe ) );
+       if ( ! pxe ) {
+               rc = -ENOMEM;
+               goto err_alloc;
+       }
+       ref_init ( &pxe->refcnt, efi_pxe_free );
+       pxe->netdev = netdev_get ( netdev );
+       pxe->name = netdev->name;
+       pxe->handle = handle;
+       memcpy ( &pxe->base, &efi_pxe_base_code_protocol, sizeof ( pxe->base ));
+       pxe->base.Mode = &pxe->mode;
+       pxe->buf.op = &efi_pxe_buf_operations;
+       intf_init ( &pxe->tftp, &efi_pxe_tftp_desc, &pxe->refcnt );
+       intf_init ( &pxe->udp, &efi_pxe_udp_desc, &pxe->refcnt );
+       INIT_LIST_HEAD ( &pxe->queue );
+       process_init_stopped ( &pxe->process, &efi_pxe_process_desc,
+                              &pxe->refcnt );
+
+       /* Crude heuristic: assume that we prefer to use IPv4 if we
+        * have an IPv4 address for the network device, otherwise
+        * prefer IPv6 (if available).
+        */
+       fetch_ipv4_setting ( netdev_settings ( netdev ), &ip_setting, &ip );
+       use_ipv6 = ( ip.s_addr ? FALSE : ( ipv6 != NULL ) );
+
+       /* Start base code */
+       efi_pxe_start ( &pxe->base, use_ipv6 );
+
+       /* Install PXE base code protocol */
+       if ( ( efirc = bs->InstallMultipleProtocolInterfaces (
+                       &handle, &efi_pxe_base_code_protocol_guid, &pxe->base,
+                       NULL ) ) != 0 ) {
+               rc = -EEFI ( efirc );
+               DBGC ( pxe, "PXE %s could not install base code protocol: %s\n",
+                      pxe->name, strerror ( rc ) );
+               goto err_install_protocol;
+       }
+
+       /* Transfer reference to list and return */
+       list_add_tail ( &pxe->list, &efi_pxes );
+       DBGC ( pxe, "PXE %s installed for %s\n",
+              pxe->name, efi_handle_name ( handle ) );
+       return 0;
+
+       bs->UninstallMultipleProtocolInterfaces (
+                       handle, &efi_pxe_base_code_protocol_guid, &pxe->base,
+                       NULL );
+ err_install_protocol:
+       ref_put ( &pxe->refcnt );
+ err_alloc:
+       return rc;
+}
+
+/**
+ * Uninstall PXE base code protocol
+ *
+ * @v handle           EFI handle
+ */
+void efi_pxe_uninstall ( EFI_HANDLE handle ) {
+       EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+       struct efi_pxe *pxe;
+
+       /* Locate PXE base code */
+       pxe = efi_pxe_find ( handle );
+       if ( ! handle ) {
+               DBG ( "PXE could not find base code for %s\n",
+                     efi_handle_name ( handle ) );
+               return;
+       }
+
+       /* Stop base code */
+       efi_pxe_stop ( &pxe->base );
+
+       /* Uninstall PXE base code protocol */
+       bs->UninstallMultipleProtocolInterfaces (
+                       handle, &efi_pxe_base_code_protocol_guid, &pxe->base,
+                       NULL );
+
+       /* Remove from list and drop list's reference */
+       list_del ( &pxe->list );
+       ref_put ( &pxe->refcnt );
+}