diff --git a/src/image/efi_image.c b/src/image/efi_image.c index b7d8f9c6..89d57bbd 100644 --- a/src/image/efi_image.c +++ b/src/image/efi_image.c @@ -29,6 +29,7 @@ FILE_LICENCE ( GPL2_OR_LATER ); #include #include #include +#include #include #include #include @@ -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 index 00000000..b356f378 --- /dev/null +++ b/src/include/ipxe/efi/efi_pxe.h @@ -0,0 +1,17 @@ +#ifndef _IPXE_EFI_PXE_H +#define _IPXE_EFI_PXE_H + +/** @file + * + * EFI PXE base code protocol + */ + +#include +#include + +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 */ diff --git a/src/include/ipxe/errfile.h b/src/include/ipxe/errfile.h index e21c9593..00f8f981 100644 --- a/src/include/ipxe/errfile.h +++ b/src/include/ipxe/errfile.h @@ -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 index 00000000..1847e3fd --- /dev/null +++ b/src/interface/efi/efi_pxe.c @@ -0,0 +1,1599 @@ +/* + * Copyright (C) 2015 Michael Brown . + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** @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 ""; + } +} + +/** + * 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, "" ); + } + 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, "" ); + } + 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, "" ); + } + 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 ); +}