/* * 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 #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; /** Apple NetBoot protocol */ EFI_APPLE_NET_BOOT_PROTOCOL apple; /** 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, }; /****************************************************************************** * * Apple NetBoot protocol * ****************************************************************************** */ /** * Get DHCP/BSDP response * * @v packet Packet * @v len Length of data buffer * @v data Data buffer * @ret efirc EFI status code */ static EFI_STATUS EFIAPI efi_apple_get_response ( EFI_PXE_BASE_CODE_PACKET *packet, UINTN *len, VOID *data ) { /* Check length */ if ( *len < sizeof ( *packet ) ) { *len = sizeof ( *packet ); return EFI_BUFFER_TOO_SMALL; } /* Copy packet */ memcpy ( data, packet, sizeof ( *packet ) ); *len = sizeof ( *packet ); return EFI_SUCCESS; } /** * Get DHCP response * * @v apple Apple NetBoot protocol * @v len Length of data buffer * @v data Data buffer * @ret efirc EFI status code */ static EFI_STATUS EFIAPI efi_apple_get_dhcp_response ( EFI_APPLE_NET_BOOT_PROTOCOL *apple, UINTN *len, VOID *data ) { struct efi_pxe *pxe = container_of ( apple, struct efi_pxe, apple ); return efi_apple_get_response ( &pxe->mode.DhcpAck, len, data ); } /** * Get BSDP response * * @v apple Apple NetBoot protocol * @v len Length of data buffer * @v data Data buffer * @ret efirc EFI status code */ static EFI_STATUS EFIAPI efi_apple_get_bsdp_response ( EFI_APPLE_NET_BOOT_PROTOCOL *apple, UINTN *len, VOID *data ) { struct efi_pxe *pxe = container_of ( apple, struct efi_pxe, apple ); return efi_apple_get_response ( &pxe->mode.PxeReply, len, data ); } /** Apple NetBoot protocol */ static EFI_APPLE_NET_BOOT_PROTOCOL efi_apple_net_boot_protocol = { .GetDhcpResponse = efi_apple_get_dhcp_response, .GetBsdpResponse = efi_apple_get_bsdp_response, }; /****************************************************************************** * * Installer * ****************************************************************************** */ /** * 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; memcpy ( &pxe->apple, &efi_apple_net_boot_protocol, sizeof ( pxe->apple ) ); 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, &efi_apple_net_boot_protocol_guid, &pxe->apple, 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, &efi_apple_net_boot_protocol_guid, &pxe->apple, 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, &efi_apple_net_boot_protocol_guid, &pxe->apple, NULL ); /* Remove from list and drop list's reference */ list_del ( &pxe->list ); ref_put ( &pxe->refcnt ); }