| /* |
| * Copyright (C) 2008 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 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. |
| */ |
| |
| FILE_LICENCE ( GPL2_OR_LATER ); |
| |
| #include <stdlib.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <assert.h> |
| #include <byteswap.h> |
| #include <ipxe/netdevice.h> |
| #include <ipxe/vlan.h> |
| #include <ipxe/iobuf.h> |
| #include <ipxe/in.h> |
| #include <ipxe/version.h> |
| #include <ipxe/console.h> |
| #include <ipxe/efi/efi.h> |
| #include <ipxe/efi/efi_driver.h> |
| #include <ipxe/efi/efi_strings.h> |
| #include <ipxe/efi/efi_path.h> |
| #include <ipxe/efi/efi_utils.h> |
| #include <ipxe/efi/efi_watchdog.h> |
| #include <ipxe/efi/efi_null.h> |
| #include <ipxe/efi/efi_snp.h> |
| #include <usr/autoboot.h> |
| #include <config/general.h> |
| |
| /** List of SNP devices */ |
| static LIST_HEAD ( efi_snp_devices ); |
| |
| /** Network devices are currently claimed for use by iPXE */ |
| static int efi_snp_claimed; |
| |
| /** TPL prior to network devices being claimed */ |
| static struct efi_saved_tpl efi_snp_saved_tpl; |
| |
| /* Downgrade user experience if configured to do so |
| * |
| * The default UEFI user experience for network boot is somewhat |
| * excremental: only TFTP is available as a download protocol, and if |
| * anything goes wrong the user will be shown just a dot on an |
| * otherwise blank screen. (Some programmer was clearly determined to |
| * win a bet that they could outshine Apple at producing uninformative |
| * error messages.) |
| * |
| * For comparison, the default iPXE user experience provides the |
| * option to use protocols designed more recently than 1980 (such as |
| * HTTP and iSCSI), and if anything goes wrong the the user will be |
| * shown one of over 1200 different error messages, complete with a |
| * link to a wiki page describing that specific error. |
| * |
| * We default to upgrading the user experience to match that available |
| * in a "legacy" BIOS environment, by installing our own instance of |
| * EFI_LOAD_FILE_PROTOCOL. |
| * |
| * Note that unfortunately we can't sensibly provide the choice of |
| * both options to the user in the same build, because the UEFI boot |
| * menu ignores the multitude of ways in which a network device handle |
| * can be described and opaquely labels both menu entries as just "EFI |
| * Network". |
| */ |
| #ifdef EFI_DOWNGRADE_UX |
| static EFI_GUID dummy_load_file_protocol_guid = { |
| 0x6f6c7323, 0x2077, 0x7523, |
| { 0x6e, 0x68, 0x65, 0x6c, 0x70, 0x66, 0x75, 0x6c } |
| }; |
| #define efi_load_file_protocol_guid dummy_load_file_protocol_guid |
| #endif |
| |
| /** |
| * Set EFI SNP mode state |
| * |
| * @v snp SNP interface |
| */ |
| static void efi_snp_set_state ( struct efi_snp_device *snpdev ) { |
| struct net_device *netdev = snpdev->netdev; |
| EFI_SIMPLE_NETWORK_MODE *mode = &snpdev->mode; |
| |
| /* Calculate state */ |
| if ( ! snpdev->started ) { |
| /* Start() method not called; report as Stopped */ |
| mode->State = EfiSimpleNetworkStopped; |
| } else if ( ! netdev_is_open ( netdev ) ) { |
| /* Network device not opened; report as Started */ |
| mode->State = EfiSimpleNetworkStarted; |
| } else if ( efi_snp_claimed ) { |
| /* Network device opened but claimed for use by iPXE; report |
| * as Started to inhibit receive polling. |
| */ |
| mode->State = EfiSimpleNetworkStarted; |
| } else { |
| /* Network device opened and available for use via SNP; report |
| * as Initialized. |
| */ |
| mode->State = EfiSimpleNetworkInitialized; |
| } |
| } |
| |
| /** |
| * Set EFI SNP mode based on iPXE net device parameters |
| * |
| * @v snp SNP interface |
| */ |
| static void efi_snp_set_mode ( struct efi_snp_device *snpdev ) { |
| struct net_device *netdev = snpdev->netdev; |
| EFI_SIMPLE_NETWORK_MODE *mode = &snpdev->mode; |
| struct ll_protocol *ll_protocol = netdev->ll_protocol; |
| unsigned int ll_addr_len = ll_protocol->ll_addr_len; |
| |
| mode->HwAddressSize = ll_addr_len; |
| mode->MediaHeaderSize = ll_protocol->ll_header_len; |
| mode->MaxPacketSize = netdev->mtu; |
| mode->ReceiveFilterMask = ( EFI_SIMPLE_NETWORK_RECEIVE_UNICAST | |
| EFI_SIMPLE_NETWORK_RECEIVE_MULTICAST | |
| EFI_SIMPLE_NETWORK_RECEIVE_BROADCAST ); |
| assert ( ll_addr_len <= sizeof ( mode->CurrentAddress ) ); |
| memcpy ( &mode->CurrentAddress, netdev->ll_addr, ll_addr_len ); |
| memcpy ( &mode->BroadcastAddress, netdev->ll_broadcast, ll_addr_len ); |
| ll_protocol->init_addr ( netdev->hw_addr, &mode->PermanentAddress ); |
| mode->IfType = ntohs ( ll_protocol->ll_proto ); |
| mode->MacAddressChangeable = TRUE; |
| mode->MediaPresentSupported = TRUE; |
| mode->MediaPresent = ( netdev_link_ok ( netdev ) ? TRUE : FALSE ); |
| } |
| |
| /** |
| * Flush transmit ring and receive queue |
| * |
| * @v snpdev SNP device |
| */ |
| static void efi_snp_flush ( struct efi_snp_device *snpdev ) { |
| struct io_buffer *iobuf; |
| struct io_buffer *tmp; |
| |
| /* Reset transmit completion ring */ |
| snpdev->tx_prod = 0; |
| snpdev->tx_cons = 0; |
| |
| /* Discard any queued receive buffers */ |
| list_for_each_entry_safe ( iobuf, tmp, &snpdev->rx, list ) { |
| list_del ( &iobuf->list ); |
| free_iob ( iobuf ); |
| } |
| } |
| |
| /** |
| * Poll net device and count received packets |
| * |
| * @v snpdev SNP device |
| */ |
| static void efi_snp_poll ( struct efi_snp_device *snpdev ) { |
| EFI_BOOT_SERVICES *bs = efi_systab->BootServices; |
| struct io_buffer *iobuf; |
| |
| /* Poll network device */ |
| netdev_poll ( snpdev->netdev ); |
| |
| /* Retrieve any received packets */ |
| while ( ( iobuf = netdev_rx_dequeue ( snpdev->netdev ) ) ) { |
| list_add_tail ( &iobuf->list, &snpdev->rx ); |
| snpdev->interrupts |= EFI_SIMPLE_NETWORK_RECEIVE_INTERRUPT; |
| bs->SignalEvent ( &snpdev->snp.WaitForPacket ); |
| } |
| } |
| |
| /** |
| * Change SNP state from "stopped" to "started" |
| * |
| * @v snp SNP interface |
| * @ret efirc EFI status code |
| */ |
| static EFI_STATUS EFIAPI |
| efi_snp_start ( EFI_SIMPLE_NETWORK_PROTOCOL *snp ) { |
| struct efi_snp_device *snpdev = |
| container_of ( snp, struct efi_snp_device, snp ); |
| |
| DBGC ( snpdev, "SNPDEV %p START\n", snpdev ); |
| |
| /* Allow start even if net device is currently claimed by iPXE */ |
| if ( efi_snp_claimed ) { |
| DBGC ( snpdev, "SNPDEV %p allowing start while claimed\n", |
| snpdev ); |
| } |
| |
| snpdev->started = 1; |
| efi_snp_set_state ( snpdev ); |
| return 0; |
| } |
| |
| /** |
| * Change SNP state from "started" to "stopped" |
| * |
| * @v snp SNP interface |
| * @ret efirc EFI status code |
| */ |
| static EFI_STATUS EFIAPI |
| efi_snp_stop ( EFI_SIMPLE_NETWORK_PROTOCOL *snp ) { |
| struct efi_snp_device *snpdev = |
| container_of ( snp, struct efi_snp_device, snp ); |
| |
| DBGC ( snpdev, "SNPDEV %p STOP\n", snpdev ); |
| |
| /* Fail if net device is currently claimed for use by iPXE */ |
| if ( efi_snp_claimed ) |
| return EFI_NOT_READY; |
| |
| snpdev->started = 0; |
| efi_snp_set_state ( snpdev ); |
| |
| return 0; |
| } |
| |
| /** |
| * Open the network device |
| * |
| * @v snp SNP interface |
| * @v extra_rx_bufsize Extra RX buffer size, in bytes |
| * @v extra_tx_bufsize Extra TX buffer size, in bytes |
| * @ret efirc EFI status code |
| */ |
| static EFI_STATUS EFIAPI |
| efi_snp_initialize ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, |
| UINTN extra_rx_bufsize, UINTN extra_tx_bufsize ) { |
| struct efi_snp_device *snpdev = |
| container_of ( snp, struct efi_snp_device, snp ); |
| struct efi_saved_tpl tpl; |
| int rc; |
| |
| DBGC ( snpdev, "SNPDEV %p INITIALIZE (%ld extra RX, %ld extra TX)\n", |
| snpdev, ( ( unsigned long ) extra_rx_bufsize ), |
| ( ( unsigned long ) extra_tx_bufsize ) ); |
| |
| /* Do nothing if net device is currently claimed for use by |
| * iPXE. Do not return an error, because this will cause |
| * MnpDxe et al to fail to install the relevant child handles |
| * and to leave behind a partially initialised device handle |
| * that can cause a later system crash. |
| */ |
| if ( efi_snp_claimed ) { |
| DBGC ( snpdev, "SNPDEV %p ignoring initialization while " |
| "claimed\n", snpdev ); |
| return 0; |
| } |
| |
| /* Raise TPL */ |
| efi_raise_tpl ( &tpl ); |
| |
| /* Open network device */ |
| if ( ( rc = netdev_open ( snpdev->netdev ) ) != 0 ) { |
| DBGC ( snpdev, "SNPDEV %p could not open %s: %s\n", |
| snpdev, snpdev->netdev->name, strerror ( rc ) ); |
| goto err_open; |
| } |
| efi_snp_set_state ( snpdev ); |
| |
| err_open: |
| efi_restore_tpl ( &tpl ); |
| return EFIRC ( rc ); |
| } |
| |
| /** |
| * Reset the network device |
| * |
| * @v snp SNP interface |
| * @v ext_verify Extended verification required |
| * @ret efirc EFI status code |
| */ |
| static EFI_STATUS EFIAPI |
| efi_snp_reset ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, BOOLEAN ext_verify ) { |
| struct efi_snp_device *snpdev = |
| container_of ( snp, struct efi_snp_device, snp ); |
| struct efi_saved_tpl tpl; |
| int rc; |
| |
| DBGC ( snpdev, "SNPDEV %p RESET (%s extended verification)\n", |
| snpdev, ( ext_verify ? "with" : "without" ) ); |
| |
| /* Fail if net device is currently claimed for use by iPXE */ |
| if ( efi_snp_claimed ) { |
| rc = -EAGAIN; |
| goto err_claimed; |
| } |
| |
| /* Raise TPL */ |
| efi_raise_tpl ( &tpl ); |
| |
| /* Close network device */ |
| netdev_close ( snpdev->netdev ); |
| efi_snp_set_state ( snpdev ); |
| efi_snp_flush ( snpdev ); |
| |
| /* Reopen network device */ |
| if ( ( rc = netdev_open ( snpdev->netdev ) ) != 0 ) { |
| DBGC ( snpdev, "SNPDEV %p could not reopen %s: %s\n", |
| snpdev, snpdev->netdev->name, strerror ( rc ) ); |
| goto err_open; |
| } |
| efi_snp_set_state ( snpdev ); |
| |
| err_open: |
| efi_restore_tpl ( &tpl ); |
| err_claimed: |
| return EFIRC ( rc ); |
| } |
| |
| /** |
| * Shut down the network device |
| * |
| * @v snp SNP interface |
| * @ret efirc EFI status code |
| */ |
| static EFI_STATUS EFIAPI |
| efi_snp_shutdown ( EFI_SIMPLE_NETWORK_PROTOCOL *snp ) { |
| struct efi_snp_device *snpdev = |
| container_of ( snp, struct efi_snp_device, snp ); |
| struct efi_saved_tpl tpl; |
| |
| DBGC ( snpdev, "SNPDEV %p SHUTDOWN\n", snpdev ); |
| |
| /* Fail if net device is currently claimed for use by iPXE */ |
| if ( efi_snp_claimed ) |
| return EFI_NOT_READY; |
| |
| /* Raise TPL */ |
| efi_raise_tpl ( &tpl ); |
| |
| /* Close network device */ |
| netdev_close ( snpdev->netdev ); |
| efi_snp_set_state ( snpdev ); |
| efi_snp_flush ( snpdev ); |
| |
| /* Restore TPL */ |
| efi_restore_tpl ( &tpl ); |
| |
| return 0; |
| } |
| |
| /** |
| * Manage receive filters |
| * |
| * @v snp SNP interface |
| * @v enable Receive filters to enable |
| * @v disable Receive filters to disable |
| * @v mcast_reset Reset multicast filters |
| * @v mcast_count Number of multicast filters |
| * @v mcast Multicast filters |
| * @ret efirc EFI status code |
| */ |
| static EFI_STATUS EFIAPI |
| efi_snp_receive_filters ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, UINT32 enable, |
| UINT32 disable, BOOLEAN mcast_reset, |
| UINTN mcast_count, EFI_MAC_ADDRESS *mcast ) { |
| struct efi_snp_device *snpdev = |
| container_of ( snp, struct efi_snp_device, snp ); |
| unsigned int i; |
| |
| DBGC ( snpdev, "SNPDEV %p RECEIVE_FILTERS %08x&~%08x%s %ld mcast\n", |
| snpdev, enable, disable, ( mcast_reset ? " reset" : "" ), |
| ( ( unsigned long ) mcast_count ) ); |
| for ( i = 0 ; i < mcast_count ; i++ ) { |
| DBGC2_HDA ( snpdev, i, &mcast[i], |
| snpdev->netdev->ll_protocol->ll_addr_len ); |
| } |
| |
| /* Lie through our teeth, otherwise MNP refuses to accept us. |
| * |
| * Return success even if the SNP device is currently claimed |
| * for use by iPXE, since otherwise Windows Deployment |
| * Services refuses to attempt to receive further packets via |
| * our EFI PXE Base Code protocol. |
| */ |
| return 0; |
| } |
| |
| /** |
| * Set station address |
| * |
| * @v snp SNP interface |
| * @v reset Reset to permanent address |
| * @v new New station address |
| * @ret efirc EFI status code |
| */ |
| static EFI_STATUS EFIAPI |
| efi_snp_station_address ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, BOOLEAN reset, |
| EFI_MAC_ADDRESS *new ) { |
| struct efi_snp_device *snpdev = |
| container_of ( snp, struct efi_snp_device, snp ); |
| struct ll_protocol *ll_protocol = snpdev->netdev->ll_protocol; |
| |
| DBGC ( snpdev, "SNPDEV %p STATION_ADDRESS %s\n", snpdev, |
| ( reset ? "reset" : ll_protocol->ntoa ( new ) ) ); |
| |
| /* Fail if net device is currently claimed for use by iPXE */ |
| if ( efi_snp_claimed ) |
| return EFI_NOT_READY; |
| |
| /* Set the MAC address */ |
| if ( reset ) |
| new = &snpdev->mode.PermanentAddress; |
| memcpy ( snpdev->netdev->ll_addr, new, ll_protocol->ll_addr_len ); |
| |
| /* MAC address changes take effect only on netdev_open() */ |
| if ( netdev_is_open ( snpdev->netdev ) ) { |
| DBGC ( snpdev, "SNPDEV %p MAC address changed while net " |
| "device open\n", snpdev ); |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Get (or reset) statistics |
| * |
| * @v snp SNP interface |
| * @v reset Reset statistics |
| * @v stats_len Size of statistics table |
| * @v stats Statistics table |
| * @ret efirc EFI status code |
| */ |
| static EFI_STATUS EFIAPI |
| efi_snp_statistics ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, BOOLEAN reset, |
| UINTN *stats_len, EFI_NETWORK_STATISTICS *stats ) { |
| struct efi_snp_device *snpdev = |
| container_of ( snp, struct efi_snp_device, snp ); |
| EFI_NETWORK_STATISTICS stats_buf; |
| |
| DBGC ( snpdev, "SNPDEV %p STATISTICS%s", snpdev, |
| ( reset ? " reset" : "" ) ); |
| |
| /* Fail if net device is currently claimed for use by iPXE */ |
| if ( efi_snp_claimed ) |
| return EFI_NOT_READY; |
| |
| /* Gather statistics */ |
| memset ( &stats_buf, 0, sizeof ( stats_buf ) ); |
| stats_buf.TxGoodFrames = snpdev->netdev->tx_stats.good; |
| stats_buf.TxDroppedFrames = snpdev->netdev->tx_stats.bad; |
| stats_buf.TxTotalFrames = ( snpdev->netdev->tx_stats.good + |
| snpdev->netdev->tx_stats.bad ); |
| stats_buf.RxGoodFrames = snpdev->netdev->rx_stats.good; |
| stats_buf.RxDroppedFrames = snpdev->netdev->rx_stats.bad; |
| stats_buf.RxTotalFrames = ( snpdev->netdev->rx_stats.good + |
| snpdev->netdev->rx_stats.bad ); |
| if ( *stats_len > sizeof ( stats_buf ) ) |
| *stats_len = sizeof ( stats_buf ); |
| if ( stats ) |
| memcpy ( stats, &stats_buf, *stats_len ); |
| |
| /* Reset statistics if requested to do so */ |
| if ( reset ) { |
| memset ( &snpdev->netdev->tx_stats, 0, |
| sizeof ( snpdev->netdev->tx_stats ) ); |
| memset ( &snpdev->netdev->rx_stats, 0, |
| sizeof ( snpdev->netdev->rx_stats ) ); |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Convert multicast IP address to MAC address |
| * |
| * @v snp SNP interface |
| * @v ipv6 Address is IPv6 |
| * @v ip IP address |
| * @v mac MAC address |
| * @ret efirc EFI status code |
| */ |
| static EFI_STATUS EFIAPI |
| efi_snp_mcast_ip_to_mac ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, BOOLEAN ipv6, |
| EFI_IP_ADDRESS *ip, EFI_MAC_ADDRESS *mac ) { |
| struct efi_snp_device *snpdev = |
| container_of ( snp, struct efi_snp_device, snp ); |
| struct ll_protocol *ll_protocol = snpdev->netdev->ll_protocol; |
| const char *ip_str; |
| int rc; |
| |
| ip_str = ( ipv6 ? "(IPv6)" /* FIXME when we have inet6_ntoa() */ : |
| inet_ntoa ( *( ( struct in_addr * ) ip ) ) ); |
| DBGC ( snpdev, "SNPDEV %p MCAST_IP_TO_MAC %s\n", snpdev, ip_str ); |
| |
| /* Fail if net device is currently claimed for use by iPXE */ |
| if ( efi_snp_claimed ) |
| return EFI_NOT_READY; |
| |
| /* Try to hash the address */ |
| if ( ( rc = ll_protocol->mc_hash ( ( ipv6 ? AF_INET6 : AF_INET ), |
| ip, mac ) ) != 0 ) { |
| DBGC ( snpdev, "SNPDEV %p could not hash %s: %s\n", |
| snpdev, ip_str, strerror ( rc ) ); |
| return EFIRC ( rc ); |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Read or write non-volatile storage |
| * |
| * @v snp SNP interface |
| * @v read Operation is a read |
| * @v offset Starting offset within NVRAM |
| * @v len Length of data buffer |
| * @v data Data buffer |
| * @ret efirc EFI status code |
| */ |
| static EFI_STATUS EFIAPI |
| efi_snp_nvdata ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, BOOLEAN read, |
| UINTN offset, UINTN len, VOID *data ) { |
| struct efi_snp_device *snpdev = |
| container_of ( snp, struct efi_snp_device, snp ); |
| |
| DBGC ( snpdev, "SNPDEV %p NVDATA %s %lx+%lx\n", snpdev, |
| ( read ? "read" : "write" ), ( ( unsigned long ) offset ), |
| ( ( unsigned long ) len ) ); |
| if ( ! read ) |
| DBGC2_HDA ( snpdev, offset, data, len ); |
| |
| /* Fail if net device is currently claimed for use by iPXE */ |
| if ( efi_snp_claimed ) |
| return EFI_NOT_READY; |
| |
| return EFI_UNSUPPORTED; |
| } |
| |
| /** |
| * Read interrupt status and TX recycled buffer status |
| * |
| * @v snp SNP interface |
| * @v interrupts Interrupt status, or NULL |
| * @v txbuf Recycled transmit buffer address, or NULL |
| * @ret efirc EFI status code |
| */ |
| static EFI_STATUS EFIAPI |
| efi_snp_get_status ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, |
| UINT32 *interrupts, VOID **txbuf ) { |
| struct efi_snp_device *snpdev = |
| container_of ( snp, struct efi_snp_device, snp ); |
| struct efi_saved_tpl tpl; |
| |
| DBGC2 ( snpdev, "SNPDEV %p GET_STATUS", snpdev ); |
| |
| /* Fail if net device is currently claimed for use by iPXE */ |
| if ( efi_snp_claimed ) { |
| DBGC2 ( snpdev, "\n" ); |
| return EFI_NOT_READY; |
| } |
| |
| /* Raise TPL */ |
| efi_raise_tpl ( &tpl ); |
| |
| /* Poll the network device */ |
| efi_snp_poll ( snpdev ); |
| |
| /* Interrupt status. In practice, this seems to be used only |
| * to detect TX completions. |
| */ |
| if ( interrupts ) { |
| *interrupts = snpdev->interrupts; |
| DBGC2 ( snpdev, " INTS:%02x", *interrupts ); |
| snpdev->interrupts = 0; |
| } |
| |
| /* TX completions */ |
| if ( txbuf ) { |
| if ( snpdev->tx_prod != snpdev->tx_cons ) { |
| *txbuf = snpdev->tx[snpdev->tx_cons++ % EFI_SNP_NUM_TX]; |
| } else { |
| *txbuf = NULL; |
| } |
| DBGC2 ( snpdev, " TX:%p", *txbuf ); |
| } |
| |
| /* Restore TPL */ |
| efi_restore_tpl ( &tpl ); |
| |
| DBGC2 ( snpdev, "\n" ); |
| return 0; |
| } |
| |
| /** |
| * Start packet transmission |
| * |
| * @v snp SNP interface |
| * @v ll_header_len Link-layer header length, if to be filled in |
| * @v len Length of data buffer |
| * @v data Data buffer |
| * @v ll_src Link-layer source address, if specified |
| * @v ll_dest Link-layer destination address, if specified |
| * @v net_proto Network-layer protocol (in host order) |
| * @ret efirc EFI status code |
| */ |
| static EFI_STATUS EFIAPI |
| efi_snp_transmit ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, |
| UINTN ll_header_len, UINTN len, VOID *data, |
| EFI_MAC_ADDRESS *ll_src, EFI_MAC_ADDRESS *ll_dest, |
| UINT16 *net_proto ) { |
| struct efi_snp_device *snpdev = |
| container_of ( snp, struct efi_snp_device, snp ); |
| struct ll_protocol *ll_protocol = snpdev->netdev->ll_protocol; |
| struct efi_saved_tpl tpl; |
| struct io_buffer *iobuf; |
| size_t payload_len; |
| unsigned int tx_fill; |
| int rc; |
| |
| DBGC2 ( snpdev, "SNPDEV %p TRANSMIT %p+%lx", snpdev, data, |
| ( ( unsigned long ) len ) ); |
| if ( ll_header_len ) { |
| if ( ll_src ) { |
| DBGC2 ( snpdev, " src %s", |
| ll_protocol->ntoa ( ll_src ) ); |
| } |
| if ( ll_dest ) { |
| DBGC2 ( snpdev, " dest %s", |
| ll_protocol->ntoa ( ll_dest ) ); |
| } |
| if ( net_proto ) { |
| DBGC2 ( snpdev, " proto %04x", *net_proto ); |
| } |
| } |
| DBGC2 ( snpdev, "\n" ); |
| |
| /* Fail if net device is currently claimed for use by iPXE */ |
| if ( efi_snp_claimed ) { |
| rc = -EAGAIN; |
| goto err_claimed; |
| } |
| |
| /* Raise TPL */ |
| efi_raise_tpl ( &tpl ); |
| |
| /* Sanity checks */ |
| if ( ll_header_len ) { |
| if ( ll_header_len != ll_protocol->ll_header_len ) { |
| DBGC ( snpdev, "SNPDEV %p TX invalid header length " |
| "%ld\n", snpdev, |
| ( ( unsigned long ) ll_header_len ) ); |
| rc = -EINVAL; |
| goto err_sanity; |
| } |
| if ( len < ll_header_len ) { |
| DBGC ( snpdev, "SNPDEV %p invalid packet length %ld\n", |
| snpdev, ( ( unsigned long ) len ) ); |
| rc = -EINVAL; |
| goto err_sanity; |
| } |
| if ( ! ll_dest ) { |
| DBGC ( snpdev, "SNPDEV %p TX missing destination " |
| "address\n", snpdev ); |
| rc = -EINVAL; |
| goto err_sanity; |
| } |
| if ( ! net_proto ) { |
| DBGC ( snpdev, "SNPDEV %p TX missing network " |
| "protocol\n", snpdev ); |
| rc = -EINVAL; |
| goto err_sanity; |
| } |
| if ( ! ll_src ) |
| ll_src = &snpdev->mode.CurrentAddress; |
| } |
| |
| /* Allocate buffer */ |
| payload_len = ( len - ll_protocol->ll_header_len ); |
| iobuf = alloc_iob ( MAX_LL_HEADER_LEN + ( ( payload_len > IOB_ZLEN ) ? |
| payload_len : IOB_ZLEN ) ); |
| if ( ! iobuf ) { |
| DBGC ( snpdev, "SNPDEV %p TX could not allocate %ld-byte " |
| "buffer\n", snpdev, ( ( unsigned long ) len ) ); |
| rc = -ENOMEM; |
| goto err_alloc_iob; |
| } |
| iob_reserve ( iobuf, ( MAX_LL_HEADER_LEN - |
| ll_protocol->ll_header_len ) ); |
| memcpy ( iob_put ( iobuf, len ), data, len ); |
| |
| /* Create link-layer header, if specified */ |
| if ( ll_header_len ) { |
| iob_pull ( iobuf, ll_protocol->ll_header_len ); |
| if ( ( rc = ll_protocol->push ( snpdev->netdev, |
| iobuf, ll_dest, ll_src, |
| htons ( *net_proto ) )) != 0 ){ |
| DBGC ( snpdev, "SNPDEV %p TX could not construct " |
| "header: %s\n", snpdev, strerror ( rc ) ); |
| goto err_ll_push; |
| } |
| } |
| |
| /* Transmit packet */ |
| if ( ( rc = netdev_tx ( snpdev->netdev, iob_disown ( iobuf ) ) ) != 0){ |
| DBGC ( snpdev, "SNPDEV %p TX could not transmit: %s\n", |
| snpdev, strerror ( rc ) ); |
| goto err_tx; |
| } |
| |
| /* Record in transmit completion ring. If we run out of |
| * space, report the failure even though we have already |
| * transmitted the packet. |
| * |
| * This allows us to report completions only for packets for |
| * which we had reported successfully initiating transmission, |
| * while continuing to support clients that never poll for |
| * transmit completions. |
| */ |
| tx_fill = ( snpdev->tx_prod - snpdev->tx_cons ); |
| if ( tx_fill >= EFI_SNP_NUM_TX ) { |
| DBGC ( snpdev, "SNPDEV %p TX completion ring full\n", snpdev ); |
| rc = -ENOBUFS; |
| goto err_ring_full; |
| } |
| snpdev->tx[ snpdev->tx_prod++ % EFI_SNP_NUM_TX ] = data; |
| snpdev->interrupts |= EFI_SIMPLE_NETWORK_TRANSMIT_INTERRUPT; |
| |
| /* Restore TPL */ |
| efi_restore_tpl ( &tpl ); |
| |
| return 0; |
| |
| err_ring_full: |
| err_tx: |
| err_ll_push: |
| free_iob ( iobuf ); |
| err_alloc_iob: |
| err_sanity: |
| efi_restore_tpl ( &tpl ); |
| err_claimed: |
| return EFIRC ( rc ); |
| } |
| |
| /** |
| * Receive packet |
| * |
| * @v snp SNP interface |
| * @v ll_header_len Link-layer header length, if to be filled in |
| * @v len Length of data buffer |
| * @v data Data buffer |
| * @v ll_src Link-layer source address, if specified |
| * @v ll_dest Link-layer destination address, if specified |
| * @v net_proto Network-layer protocol (in host order) |
| * @ret efirc EFI status code |
| */ |
| static EFI_STATUS EFIAPI |
| efi_snp_receive ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, |
| UINTN *ll_header_len, UINTN *len, VOID *data, |
| EFI_MAC_ADDRESS *ll_src, EFI_MAC_ADDRESS *ll_dest, |
| UINT16 *net_proto ) { |
| struct efi_snp_device *snpdev = |
| container_of ( snp, struct efi_snp_device, snp ); |
| struct ll_protocol *ll_protocol = snpdev->netdev->ll_protocol; |
| struct efi_saved_tpl tpl; |
| struct io_buffer *iobuf; |
| const void *iob_ll_dest; |
| const void *iob_ll_src; |
| uint16_t iob_net_proto; |
| unsigned int iob_flags; |
| size_t copy_len; |
| int rc; |
| |
| DBGC2 ( snpdev, "SNPDEV %p RECEIVE %p(+%lx)", snpdev, data, |
| ( ( unsigned long ) *len ) ); |
| |
| /* Fail if net device is currently claimed for use by iPXE */ |
| if ( efi_snp_claimed ) { |
| rc = -EAGAIN; |
| goto err_claimed; |
| } |
| |
| /* Raise TPL */ |
| efi_raise_tpl ( &tpl ); |
| |
| /* Poll the network device */ |
| efi_snp_poll ( snpdev ); |
| |
| /* Check for an available packet */ |
| iobuf = list_first_entry ( &snpdev->rx, struct io_buffer, list ); |
| if ( ! iobuf ) { |
| DBGC2 ( snpdev, "\n" ); |
| rc = -EAGAIN; |
| goto out_no_packet; |
| } |
| DBGC2 ( snpdev, "+%zx\n", iob_len ( iobuf ) ); |
| |
| /* Dequeue packet */ |
| list_del ( &iobuf->list ); |
| |
| /* Return packet to caller, truncating to buffer length */ |
| copy_len = iob_len ( iobuf ); |
| if ( copy_len > *len ) |
| copy_len = *len; |
| memcpy ( data, iobuf->data, copy_len ); |
| *len = iob_len ( iobuf ); |
| |
| /* Attempt to decode link-layer header */ |
| if ( ( rc = ll_protocol->pull ( snpdev->netdev, iobuf, &iob_ll_dest, |
| &iob_ll_src, &iob_net_proto, |
| &iob_flags ) ) != 0 ) { |
| DBGC ( snpdev, "SNPDEV %p could not parse header: %s\n", |
| snpdev, strerror ( rc ) ); |
| goto out_bad_ll_header; |
| } |
| |
| /* Return link-layer header parameters to caller, if required */ |
| if ( ll_header_len ) |
| *ll_header_len = ll_protocol->ll_header_len; |
| if ( ll_src ) |
| memcpy ( ll_src, iob_ll_src, ll_protocol->ll_addr_len ); |
| if ( ll_dest ) |
| memcpy ( ll_dest, iob_ll_dest, ll_protocol->ll_addr_len ); |
| if ( net_proto ) |
| *net_proto = ntohs ( iob_net_proto ); |
| |
| /* Check buffer length */ |
| rc = ( ( copy_len == *len ) ? 0 : -ERANGE ); |
| |
| out_bad_ll_header: |
| free_iob ( iobuf ); |
| out_no_packet: |
| efi_restore_tpl ( &tpl ); |
| err_claimed: |
| return EFIRC ( rc ); |
| } |
| |
| /** |
| * Poll event |
| * |
| * @v event Event |
| * @v context Event context |
| */ |
| static VOID EFIAPI efi_snp_wait_for_packet ( EFI_EVENT event __unused, |
| VOID *context ) { |
| struct efi_snp_device *snpdev = context; |
| struct efi_saved_tpl tpl; |
| |
| DBGCP ( snpdev, "SNPDEV %p WAIT_FOR_PACKET\n", snpdev ); |
| |
| /* Do nothing unless the net device is open */ |
| if ( ! netdev_is_open ( snpdev->netdev ) ) |
| return; |
| |
| /* Do nothing if net device is currently claimed for use by iPXE */ |
| if ( efi_snp_claimed ) |
| return; |
| |
| /* Raise TPL */ |
| efi_raise_tpl ( &tpl ); |
| |
| /* Poll the network device */ |
| efi_snp_poll ( snpdev ); |
| |
| /* Restore TPL */ |
| efi_restore_tpl ( &tpl ); |
| } |
| |
| /** SNP interface */ |
| static EFI_SIMPLE_NETWORK_PROTOCOL efi_snp_device_snp = { |
| .Revision = EFI_SIMPLE_NETWORK_PROTOCOL_REVISION, |
| .Start = efi_snp_start, |
| .Stop = efi_snp_stop, |
| .Initialize = efi_snp_initialize, |
| .Reset = efi_snp_reset, |
| .Shutdown = efi_snp_shutdown, |
| .ReceiveFilters = efi_snp_receive_filters, |
| .StationAddress = efi_snp_station_address, |
| .Statistics = efi_snp_statistics, |
| .MCastIpToMac = efi_snp_mcast_ip_to_mac, |
| .NvData = efi_snp_nvdata, |
| .GetStatus = efi_snp_get_status, |
| .Transmit = efi_snp_transmit, |
| .Receive = efi_snp_receive, |
| }; |
| |
| /****************************************************************************** |
| * |
| * UNDI protocol |
| * |
| ****************************************************************************** |
| */ |
| |
| /** Union type for command parameter blocks */ |
| typedef union { |
| PXE_CPB_STATION_ADDRESS station_address; |
| PXE_CPB_FILL_HEADER fill_header; |
| PXE_CPB_FILL_HEADER_FRAGMENTED fill_header_fragmented; |
| PXE_CPB_TRANSMIT transmit; |
| PXE_CPB_RECEIVE receive; |
| } PXE_CPB_ANY; |
| |
| /** Union type for data blocks */ |
| typedef union { |
| PXE_DB_GET_INIT_INFO get_init_info; |
| PXE_DB_STATION_ADDRESS station_address; |
| PXE_DB_GET_STATUS get_status; |
| PXE_DB_RECEIVE receive; |
| } PXE_DB_ANY; |
| |
| /** |
| * Calculate UNDI byte checksum |
| * |
| * @v data Data |
| * @v len Length of data |
| * @ret sum Checksum |
| */ |
| static uint8_t efi_undi_checksum ( void *data, size_t len ) { |
| uint8_t *bytes = data; |
| uint8_t sum = 0; |
| |
| while ( len-- ) |
| sum += *bytes++; |
| return sum; |
| } |
| |
| /** |
| * Get UNDI SNP device interface number |
| * |
| * @v snpdev SNP device |
| * @ret ifnum UNDI interface number |
| */ |
| static unsigned int efi_undi_ifnum ( struct efi_snp_device *snpdev ) { |
| |
| /* iPXE network device scope IDs are one-based (leaving zero |
| * meaning "unspecified"). UNDI interface numbers are |
| * zero-based. |
| */ |
| return ( snpdev->netdev->scope_id - 1 ); |
| } |
| |
| /** |
| * Identify UNDI SNP device |
| * |
| * @v ifnum Interface number |
| * @ret snpdev SNP device, or NULL if not found |
| */ |
| static struct efi_snp_device * efi_undi_snpdev ( unsigned int ifnum ) { |
| struct efi_snp_device *snpdev; |
| |
| list_for_each_entry ( snpdev, &efi_snp_devices, list ) { |
| if ( efi_undi_ifnum ( snpdev ) == ifnum ) |
| return snpdev; |
| } |
| return NULL; |
| } |
| |
| /** |
| * Convert EFI status code to UNDI status code |
| * |
| * @v efirc EFI status code |
| * @ret statcode UNDI status code |
| */ |
| static PXE_STATCODE efi_undi_statcode ( EFI_STATUS efirc ) { |
| |
| switch ( efirc ) { |
| case EFI_INVALID_PARAMETER: return PXE_STATCODE_INVALID_PARAMETER; |
| case EFI_UNSUPPORTED: return PXE_STATCODE_UNSUPPORTED; |
| case EFI_OUT_OF_RESOURCES: return PXE_STATCODE_BUFFER_FULL; |
| case EFI_PROTOCOL_ERROR: return PXE_STATCODE_DEVICE_FAILURE; |
| case EFI_NOT_READY: return PXE_STATCODE_NO_DATA; |
| default: |
| return PXE_STATCODE_INVALID_CDB; |
| } |
| } |
| |
| /** |
| * Get state |
| * |
| * @v snpdev SNP device |
| * @v cdb Command description block |
| * @ret efirc EFI status code |
| */ |
| static EFI_STATUS efi_undi_get_state ( struct efi_snp_device *snpdev, |
| PXE_CDB *cdb ) { |
| EFI_SIMPLE_NETWORK_MODE *mode = &snpdev->mode; |
| |
| DBGC ( snpdev, "UNDI %p GET STATE\n", snpdev ); |
| |
| /* Return current state */ |
| if ( mode->State == EfiSimpleNetworkInitialized ) { |
| cdb->StatFlags |= PXE_STATFLAGS_GET_STATE_INITIALIZED; |
| } else if ( mode->State == EfiSimpleNetworkStarted ) { |
| cdb->StatFlags |= PXE_STATFLAGS_GET_STATE_STARTED; |
| } else { |
| cdb->StatFlags |= PXE_STATFLAGS_GET_STATE_STOPPED; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Start |
| * |
| * @v snpdev SNP device |
| * @ret efirc EFI status code |
| */ |
| static EFI_STATUS efi_undi_start ( struct efi_snp_device *snpdev ) { |
| EFI_STATUS efirc; |
| |
| DBGC ( snpdev, "UNDI %p START\n", snpdev ); |
| |
| /* Start SNP device */ |
| if ( ( efirc = efi_snp_start ( &snpdev->snp ) ) != 0 ) |
| return efirc; |
| |
| return 0; |
| } |
| |
| /** |
| * Stop |
| * |
| * @v snpdev SNP device |
| * @ret efirc EFI status code |
| */ |
| static EFI_STATUS efi_undi_stop ( struct efi_snp_device *snpdev ) { |
| EFI_STATUS efirc; |
| |
| DBGC ( snpdev, "UNDI %p STOP\n", snpdev ); |
| |
| /* Stop SNP device */ |
| if ( ( efirc = efi_snp_stop ( &snpdev->snp ) ) != 0 ) |
| return efirc; |
| |
| return 0; |
| } |
| |
| /** |
| * Get initialisation information |
| * |
| * @v snpdev SNP device |
| * @v cdb Command description block |
| * @v db Data block |
| * @ret efirc EFI status code |
| */ |
| static EFI_STATUS efi_undi_get_init_info ( struct efi_snp_device *snpdev, |
| PXE_CDB *cdb, |
| PXE_DB_GET_INIT_INFO *db ) { |
| struct net_device *netdev = snpdev->netdev; |
| struct ll_protocol *ll_protocol = netdev->ll_protocol; |
| |
| DBGC ( snpdev, "UNDI %p GET INIT INFO\n", snpdev ); |
| |
| /* Populate structure */ |
| memset ( db, 0, sizeof ( *db ) ); |
| db->FrameDataLen = ( netdev->max_pkt_len - ll_protocol->ll_header_len ); |
| db->MediaHeaderLen = ll_protocol->ll_header_len; |
| db->HWaddrLen = ll_protocol->ll_addr_len; |
| db->IFtype = ntohs ( ll_protocol->ll_proto ); |
| cdb->StatFlags |= ( PXE_STATFLAGS_CABLE_DETECT_SUPPORTED | |
| PXE_STATFLAGS_GET_STATUS_NO_MEDIA_SUPPORTED ); |
| |
| return 0; |
| } |
| |
| /** |
| * Initialise |
| * |
| * @v snpdev SNP device |
| * @v cdb Command description block |
| * @v efirc EFI status code |
| */ |
| static EFI_STATUS efi_undi_initialize ( struct efi_snp_device *snpdev, |
| PXE_CDB *cdb ) { |
| struct net_device *netdev = snpdev->netdev; |
| EFI_STATUS efirc; |
| |
| DBGC ( snpdev, "UNDI %p INITIALIZE\n", snpdev ); |
| |
| /* Reset SNP device */ |
| if ( ( efirc = efi_snp_initialize ( &snpdev->snp, 0, 0 ) ) != 0 ) |
| return efirc; |
| |
| /* Report link state */ |
| if ( ! netdev_link_ok ( netdev ) ) |
| cdb->StatFlags |= PXE_STATFLAGS_INITIALIZED_NO_MEDIA; |
| |
| return 0; |
| } |
| |
| /** |
| * Reset |
| * |
| * @v snpdev SNP device |
| * @v efirc EFI status code |
| */ |
| static EFI_STATUS efi_undi_reset ( struct efi_snp_device *snpdev ) { |
| EFI_STATUS efirc; |
| |
| DBGC ( snpdev, "UNDI %p RESET\n", snpdev ); |
| |
| /* Reset SNP device */ |
| if ( ( efirc = efi_snp_reset ( &snpdev->snp, 0 ) ) != 0 ) |
| return efirc; |
| |
| return 0; |
| } |
| |
| /** |
| * Shutdown |
| * |
| * @v snpdev SNP device |
| * @v efirc EFI status code |
| */ |
| static EFI_STATUS efi_undi_shutdown ( struct efi_snp_device *snpdev ) { |
| EFI_STATUS efirc; |
| |
| DBGC ( snpdev, "UNDI %p SHUTDOWN\n", snpdev ); |
| |
| /* Reset SNP device */ |
| if ( ( efirc = efi_snp_shutdown ( &snpdev->snp ) ) != 0 ) |
| return efirc; |
| |
| return 0; |
| } |
| |
| /** |
| * Get/set receive filters |
| * |
| * @v snpdev SNP device |
| * @v cdb Command description block |
| * @v efirc EFI status code |
| */ |
| static EFI_STATUS efi_undi_receive_filters ( struct efi_snp_device *snpdev, |
| PXE_CDB *cdb ) { |
| |
| DBGC ( snpdev, "UNDI %p RECEIVE FILTERS\n", snpdev ); |
| |
| /* Mark everything as supported */ |
| cdb->StatFlags |= ( PXE_STATFLAGS_RECEIVE_FILTER_UNICAST | |
| PXE_STATFLAGS_RECEIVE_FILTER_BROADCAST | |
| PXE_STATFLAGS_RECEIVE_FILTER_PROMISCUOUS | |
| PXE_STATFLAGS_RECEIVE_FILTER_ALL_MULTICAST ); |
| |
| return 0; |
| } |
| |
| /** |
| * Get/set station address |
| * |
| * @v snpdev SNP device |
| * @v cdb Command description block |
| * @v cpb Command parameter block |
| * @v efirc EFI status code |
| */ |
| static EFI_STATUS efi_undi_station_address ( struct efi_snp_device *snpdev, |
| PXE_CDB *cdb, |
| PXE_CPB_STATION_ADDRESS *cpb, |
| PXE_DB_STATION_ADDRESS *db ) { |
| struct net_device *netdev = snpdev->netdev; |
| struct ll_protocol *ll_protocol = netdev->ll_protocol; |
| void *mac; |
| int reset; |
| EFI_STATUS efirc; |
| |
| DBGC ( snpdev, "UNDI %p STATION ADDRESS\n", snpdev ); |
| |
| /* Update address if applicable */ |
| reset = ( cdb->OpFlags & PXE_OPFLAGS_STATION_ADDRESS_RESET ); |
| mac = ( cpb ? &cpb->StationAddr : NULL ); |
| if ( ( reset || mac ) && |
| ( ( efirc = efi_snp_station_address ( &snpdev->snp, reset, |
| mac ) ) != 0 ) ) |
| return efirc; |
| |
| /* Fill in current addresses, if applicable */ |
| if ( db ) { |
| memset ( db, 0, sizeof ( *db ) ); |
| memcpy ( &db->StationAddr, netdev->ll_addr, |
| ll_protocol->ll_addr_len ); |
| memcpy ( &db->BroadcastAddr, netdev->ll_broadcast, |
| ll_protocol->ll_addr_len ); |
| memcpy ( &db->PermanentAddr, netdev->hw_addr, |
| ll_protocol->hw_addr_len ); |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Get interrupt status |
| * |
| * @v snpdev SNP device |
| * @v cdb Command description block |
| * @v db Data block |
| * @v efirc EFI status code |
| */ |
| static EFI_STATUS efi_undi_get_status ( struct efi_snp_device *snpdev, |
| PXE_CDB *cdb, PXE_DB_GET_STATUS *db ) { |
| UINT32 interrupts; |
| VOID *txbuf; |
| struct io_buffer *rxbuf; |
| EFI_STATUS efirc; |
| |
| DBGC2 ( snpdev, "UNDI %p GET STATUS\n", snpdev ); |
| |
| /* Get status */ |
| if ( ( efirc = efi_snp_get_status ( &snpdev->snp, &interrupts, |
| &txbuf ) ) != 0 ) |
| return efirc; |
| |
| /* Report status */ |
| memset ( db, 0, sizeof ( *db ) ); |
| if ( interrupts & EFI_SIMPLE_NETWORK_RECEIVE_INTERRUPT ) |
| cdb->StatFlags |= PXE_STATFLAGS_GET_STATUS_RECEIVE; |
| if ( interrupts & EFI_SIMPLE_NETWORK_TRANSMIT_INTERRUPT ) |
| cdb->StatFlags |= PXE_STATFLAGS_GET_STATUS_TRANSMIT; |
| if ( txbuf ) { |
| db->TxBuffer[0] = ( ( intptr_t ) txbuf ); |
| } else { |
| cdb->StatFlags |= PXE_STATFLAGS_GET_STATUS_NO_TXBUFS_WRITTEN; |
| /* The specification states clearly that UNDI drivers |
| * should set TXBUF_QUEUE_EMPTY if all completed |
| * buffer addresses are written into the returned data |
| * block. However, SnpDxe chooses to interpret |
| * TXBUF_QUEUE_EMPTY as a synonym for |
| * NO_TXBUFS_WRITTEN, thereby rendering it entirely |
| * pointless. Work around this UEFI stupidity, as per |
| * usual. |
| */ |
| if ( snpdev->tx_prod == snpdev->tx_cons ) |
| cdb->StatFlags |= |
| PXE_STATFLAGS_GET_STATUS_TXBUF_QUEUE_EMPTY; |
| } |
| rxbuf = list_first_entry ( &snpdev->rx, struct io_buffer, list ); |
| if ( rxbuf ) |
| db->RxFrameLen = iob_len ( rxbuf ); |
| if ( ! netdev_link_ok ( snpdev->netdev ) ) |
| cdb->StatFlags |= PXE_STATFLAGS_GET_STATUS_NO_MEDIA; |
| |
| return 0; |
| } |
| |
| /** |
| * Fill header |
| * |
| * @v snpdev SNP device |
| * @v cdb Command description block |
| * @v cpb Command parameter block |
| * @v efirc EFI status code |
| */ |
| static EFI_STATUS efi_undi_fill_header ( struct efi_snp_device *snpdev, |
| PXE_CDB *cdb, PXE_CPB_ANY *cpb ) { |
| struct net_device *netdev = snpdev->netdev; |
| struct ll_protocol *ll_protocol = netdev->ll_protocol; |
| PXE_CPB_FILL_HEADER *whole = &cpb->fill_header; |
| PXE_CPB_FILL_HEADER_FRAGMENTED *fragged = &cpb->fill_header_fragmented; |
| VOID *data; |
| void *dest; |
| void *src; |
| uint16_t proto; |
| struct io_buffer iobuf; |
| int rc; |
| |
| /* SnpDxe will (pointlessly) use PXE_CPB_FILL_HEADER_FRAGMENTED |
| * even though we choose to explicitly not claim support for |
| * fragments via PXE_ROMID_IMP_FRAG_SUPPORTED. |
| */ |
| if ( cdb->OpFlags & PXE_OPFLAGS_FILL_HEADER_FRAGMENTED ) { |
| data = ( ( void * ) ( intptr_t ) fragged->FragDesc[0].FragAddr); |
| dest = &fragged->DestAddr; |
| src = &fragged->SrcAddr; |
| proto = fragged->Protocol; |
| } else { |
| data = ( ( void * ) ( intptr_t ) whole->MediaHeader ); |
| dest = &whole->DestAddr; |
| src = &whole->SrcAddr; |
| proto = whole->Protocol; |
| } |
| |
| /* Construct link-layer header */ |
| iob_populate ( &iobuf, data, 0, ll_protocol->ll_header_len ); |
| iob_reserve ( &iobuf, ll_protocol->ll_header_len ); |
| if ( ( rc = ll_protocol->push ( netdev, &iobuf, dest, src, |
| proto ) ) != 0 ) |
| return EFIRC ( rc ); |
| |
| return 0; |
| } |
| |
| /** |
| * Transmit |
| * |
| * @v snpdev SNP device |
| * @v cpb Command parameter block |
| * @v efirc EFI status code |
| */ |
| static EFI_STATUS efi_undi_transmit ( struct efi_snp_device *snpdev, |
| PXE_CPB_TRANSMIT *cpb ) { |
| VOID *data = ( ( void * ) ( intptr_t ) cpb->FrameAddr ); |
| EFI_STATUS efirc; |
| |
| DBGC2 ( snpdev, "UNDI %p TRANSMIT\n", snpdev ); |
| |
| /* Transmit packet */ |
| if ( ( efirc = efi_snp_transmit ( &snpdev->snp, 0, cpb->DataLen, |
| data, NULL, NULL, NULL ) ) != 0 ) |
| return efirc; |
| |
| return 0; |
| } |
| |
| /** |
| * Receive |
| * |
| * @v snpdev SNP device |
| * @v cpb Command parameter block |
| * @v efirc EFI status code |
| */ |
| static EFI_STATUS efi_undi_receive ( struct efi_snp_device *snpdev, |
| PXE_CPB_RECEIVE *cpb, |
| PXE_DB_RECEIVE *db ) { |
| struct net_device *netdev = snpdev->netdev; |
| struct ll_protocol *ll_protocol = netdev->ll_protocol; |
| VOID *data = ( ( void * ) ( intptr_t ) cpb->BufferAddr ); |
| UINTN hdr_len; |
| UINTN len = cpb->BufferLen; |
| EFI_MAC_ADDRESS src; |
| EFI_MAC_ADDRESS dest; |
| UINT16 proto; |
| EFI_STATUS efirc; |
| |
| DBGC2 ( snpdev, "UNDI %p RECEIVE\n", snpdev ); |
| |
| /* Receive packet */ |
| if ( ( efirc = efi_snp_receive ( &snpdev->snp, &hdr_len, &len, data, |
| &src, &dest, &proto ) ) != 0 ) |
| return efirc; |
| |
| /* Describe frame */ |
| memset ( db, 0, sizeof ( *db ) ); |
| memcpy ( &db->SrcAddr, &src, ll_protocol->ll_addr_len ); |
| memcpy ( &db->DestAddr, &dest, ll_protocol->ll_addr_len ); |
| db->FrameLen = len; |
| db->Protocol = proto; |
| db->MediaHeaderLen = ll_protocol->ll_header_len; |
| db->Type = PXE_FRAME_TYPE_PROMISCUOUS; |
| |
| return 0; |
| } |
| |
| /** UNDI entry point */ |
| static EFIAPI VOID efi_undi_issue ( UINT64 cdb_phys ) { |
| PXE_CDB *cdb = ( ( void * ) ( intptr_t ) cdb_phys ); |
| PXE_CPB_ANY *cpb = ( ( void * ) ( intptr_t ) cdb->CPBaddr ); |
| PXE_DB_ANY *db = ( ( void * ) ( intptr_t ) cdb->DBaddr ); |
| struct efi_snp_device *snpdev; |
| EFI_STATUS efirc; |
| |
| /* Identify device */ |
| snpdev = efi_undi_snpdev ( cdb->IFnum ); |
| if ( ! snpdev ) { |
| DBGC ( cdb, "UNDI invalid interface number %d\n", cdb->IFnum ); |
| cdb->StatCode = PXE_STATCODE_INVALID_CDB; |
| cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED; |
| return; |
| } |
| |
| /* Fail if net device is currently claimed for use by iPXE */ |
| if ( efi_snp_claimed ) { |
| cdb->StatCode = PXE_STATCODE_BUSY; |
| cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED; |
| return; |
| } |
| |
| /* Handle opcode */ |
| cdb->StatCode = PXE_STATCODE_SUCCESS; |
| cdb->StatFlags = PXE_STATFLAGS_COMMAND_COMPLETE; |
| switch ( cdb->OpCode ) { |
| |
| case PXE_OPCODE_GET_STATE: |
| efirc = efi_undi_get_state ( snpdev, cdb ); |
| break; |
| |
| case PXE_OPCODE_START: |
| efirc = efi_undi_start ( snpdev ); |
| break; |
| |
| case PXE_OPCODE_STOP: |
| efirc = efi_undi_stop ( snpdev ); |
| break; |
| |
| case PXE_OPCODE_GET_INIT_INFO: |
| efirc = efi_undi_get_init_info ( snpdev, cdb, |
| &db->get_init_info ); |
| break; |
| |
| case PXE_OPCODE_INITIALIZE: |
| efirc = efi_undi_initialize ( snpdev, cdb ); |
| break; |
| |
| case PXE_OPCODE_RESET: |
| efirc = efi_undi_reset ( snpdev ); |
| break; |
| |
| case PXE_OPCODE_SHUTDOWN: |
| efirc = efi_undi_shutdown ( snpdev ); |
| break; |
| |
| case PXE_OPCODE_RECEIVE_FILTERS: |
| efirc = efi_undi_receive_filters ( snpdev, cdb ); |
| break; |
| |
| case PXE_OPCODE_STATION_ADDRESS: |
| efirc = efi_undi_station_address ( snpdev, cdb, |
| &cpb->station_address, |
| &db->station_address ); |
| break; |
| |
| case PXE_OPCODE_GET_STATUS: |
| efirc = efi_undi_get_status ( snpdev, cdb, &db->get_status ); |
| break; |
| |
| case PXE_OPCODE_FILL_HEADER: |
| efirc = efi_undi_fill_header ( snpdev, cdb, cpb ); |
| break; |
| |
| case PXE_OPCODE_TRANSMIT: |
| efirc = efi_undi_transmit ( snpdev, &cpb->transmit ); |
| break; |
| |
| case PXE_OPCODE_RECEIVE: |
| efirc = efi_undi_receive ( snpdev, &cpb->receive, |
| &db->receive ); |
| break; |
| |
| default: |
| DBGC ( snpdev, "UNDI %p unsupported opcode %#04x\n", |
| snpdev, cdb->OpCode ); |
| efirc = EFI_UNSUPPORTED; |
| break; |
| } |
| |
| /* Convert EFI status code to UNDI status code */ |
| if ( efirc != 0 ) { |
| cdb->StatFlags &= ~PXE_STATFLAGS_STATUS_MASK; |
| cdb->StatFlags |= PXE_STATFLAGS_COMMAND_FAILED; |
| cdb->StatCode = efi_undi_statcode ( efirc ); |
| } |
| } |
| |
| /** UNDI interface |
| * |
| * Must be aligned on a 16-byte boundary, for no particularly good |
| * reason. |
| */ |
| static PXE_SW_UNDI efi_snp_undi __attribute__ (( aligned ( 16 ) )) = { |
| .Signature = PXE_ROMID_SIGNATURE, |
| .Len = sizeof ( efi_snp_undi ), |
| .Rev = PXE_ROMID_REV, |
| .MajorVer = PXE_ROMID_MAJORVER, |
| .MinorVer = PXE_ROMID_MINORVER, |
| .Implementation = ( PXE_ROMID_IMP_SW_VIRT_ADDR | |
| PXE_ROMID_IMP_STATION_ADDR_SETTABLE | |
| PXE_ROMID_IMP_PROMISCUOUS_MULTICAST_RX_SUPPORTED | |
| PXE_ROMID_IMP_PROMISCUOUS_RX_SUPPORTED | |
| PXE_ROMID_IMP_BROADCAST_RX_SUPPORTED | |
| PXE_ROMID_IMP_TX_COMPLETE_INT_SUPPORTED | |
| PXE_ROMID_IMP_PACKET_RX_INT_SUPPORTED ), |
| /* SnpDxe checks that BusCnt is non-zero. It makes no further |
| * use of BusCnt, and never looks as BusType[]. As with much |
| * of the EDK2 code, this check seems to serve no purpose |
| * whatsoever but must nonetheless be humoured. |
| */ |
| .BusCnt = 1, |
| .BusType[0] = PXE_BUSTYPE ( 'i', 'P', 'X', 'E' ), |
| }; |
| |
| /** Network Identification Interface (NII) */ |
| static EFI_NETWORK_INTERFACE_IDENTIFIER_PROTOCOL efi_snp_device_nii = { |
| .Revision = EFI_NETWORK_INTERFACE_IDENTIFIER_PROTOCOL_REVISION, |
| .StringId = "UNDI", |
| .Type = EfiNetworkInterfaceUndi, |
| .MajorVer = 3, |
| .MinorVer = 1, |
| .Ipv6Supported = TRUE, /* This is a raw packet interface, FFS! */ |
| }; |
| |
| /****************************************************************************** |
| * |
| * VLAN configuration protocol |
| * |
| ****************************************************************************** |
| */ |
| |
| /** |
| * Create or modify VLAN device |
| * |
| * @v vcfg VLAN configuration protocol |
| * @v tag VLAN tag |
| * @v priority Default VLAN priority |
| * @ret efirc EFI status code |
| */ |
| static EFI_STATUS EFIAPI efi_vlan_set ( EFI_VLAN_CONFIG_PROTOCOL *vcfg, |
| UINT16 tag, UINT8 priority ) { |
| struct efi_snp_device *snpdev = |
| container_of ( vcfg, struct efi_snp_device, vcfg ); |
| struct net_device *trunk = snpdev->netdev; |
| struct efi_saved_tpl tpl; |
| int rc; |
| |
| /* Raise TPL */ |
| efi_raise_tpl ( &tpl ); |
| |
| /* Create or modify VLAN device */ |
| if ( ( rc = vlan_create ( trunk, tag, priority ) ) != 0 ) { |
| DBGC ( snpdev, "SNPDEV %p could not create VLAN tag %d: %s\n", |
| snpdev, tag, strerror ( rc ) ); |
| goto err_create; |
| } |
| DBGC ( snpdev, "SNPDEV %p created VLAN tag %d priority %d\n", |
| snpdev, tag, priority ); |
| |
| err_create: |
| efi_restore_tpl ( &tpl ); |
| return EFIRC ( rc ); |
| } |
| |
| /** |
| * Find VLAN device(s) |
| * |
| * @v vcfg VLAN configuration protocol |
| * @v filter VLAN tag, or NULL to find all VLANs |
| * @v count Number of VLANs |
| * @v entries List of VLANs |
| * @ret efirc EFI status code |
| */ |
| static EFI_STATUS EFIAPI efi_vlan_find ( EFI_VLAN_CONFIG_PROTOCOL *vcfg, |
| UINT16 *filter, UINT16 *count, |
| EFI_VLAN_FIND_DATA **entries ) { |
| EFI_BOOT_SERVICES *bs = efi_systab->BootServices; |
| struct efi_snp_device *snpdev = |
| container_of ( vcfg, struct efi_snp_device, vcfg ); |
| struct net_device *trunk = snpdev->netdev; |
| struct net_device *vlan; |
| struct efi_saved_tpl tpl; |
| EFI_VLAN_FIND_DATA *entry; |
| VOID *buffer; |
| unsigned int tag; |
| unsigned int tci; |
| size_t len; |
| EFI_STATUS efirc; |
| int rc; |
| |
| /* Raise TPL */ |
| efi_raise_tpl ( &tpl ); |
| |
| /* Count number of matching VLANs */ |
| *count = 0; |
| for ( tag = 1 ; VLAN_TAG_IS_VALID ( tag ) ; tag++ ) { |
| if ( filter && ( tag != *filter ) ) |
| continue; |
| if ( ! ( vlan = vlan_find ( trunk, tag ) ) ) |
| continue; |
| (*count)++; |
| } |
| |
| /* Allocate buffer to hold results */ |
| len = ( (*count) * sizeof ( *entry ) ); |
| if ( ( efirc = bs->AllocatePool ( EfiBootServicesData, len, |
| &buffer ) ) != 0 ) { |
| rc = -EEFI ( efirc ); |
| goto err_alloc; |
| } |
| |
| /* Fill in buffer */ |
| *entries = buffer; |
| entry = *entries; |
| for ( tag = 1 ; VLAN_TAG_IS_VALID ( tag ) ; tag++ ) { |
| if ( filter && ( tag != *filter ) ) |
| continue; |
| if ( ! ( vlan = vlan_find ( trunk, tag ) ) ) |
| continue; |
| tci = vlan_tci ( vlan ); |
| entry->VlanId = VLAN_TAG ( tci ); |
| entry->Priority = VLAN_PRIORITY ( tci ); |
| assert ( entry->VlanId == tag ); |
| entry++; |
| } |
| assert ( entry == &(*entries)[*count] ); |
| |
| /* Success */ |
| rc = 0; |
| |
| err_alloc: |
| efi_restore_tpl ( &tpl ); |
| return EFIRC ( rc ); |
| } |
| |
| /** |
| * Remove VLAN device |
| * |
| * @v vcfg VLAN configuration protocol |
| * @v tag VLAN tag |
| * @ret efirc EFI status code |
| */ |
| static EFI_STATUS EFIAPI efi_vlan_remove ( EFI_VLAN_CONFIG_PROTOCOL *vcfg, |
| UINT16 tag ) { |
| struct efi_snp_device *snpdev = |
| container_of ( vcfg, struct efi_snp_device, vcfg ); |
| struct net_device *trunk = snpdev->netdev; |
| struct net_device *vlan; |
| struct efi_saved_tpl tpl; |
| int rc; |
| |
| /* Raise TPL */ |
| efi_raise_tpl ( &tpl ); |
| |
| /* Identify VLAN device */ |
| vlan = vlan_find ( trunk, tag ); |
| if ( ! vlan ) { |
| DBGC ( snpdev, "SNPDEV %p could not find VLAN tag %d\n", |
| snpdev, tag ); |
| rc = -ENOENT; |
| goto err_find; |
| } |
| |
| /* Remove VLAN device */ |
| vlan_destroy ( vlan ); |
| DBGC ( snpdev, "SNPDEV %p removed VLAN tag %d\n", snpdev, tag ); |
| |
| /* Success */ |
| rc = 0; |
| |
| err_find: |
| efi_restore_tpl ( &tpl ); |
| return EFIRC ( rc ); |
| } |
| |
| /** VLAN configuration protocol */ |
| static EFI_VLAN_CONFIG_PROTOCOL efi_vlan = { |
| .Set = efi_vlan_set, |
| .Find = efi_vlan_find, |
| .Remove = efi_vlan_remove, |
| }; |
| |
| /****************************************************************************** |
| * |
| * Component name protocol |
| * |
| ****************************************************************************** |
| */ |
| |
| /** |
| * Look up driver name |
| * |
| * @v name2 Component name protocol |
| * @v language Language to use |
| * @v driver_name Driver name to fill in |
| * @ret efirc EFI status code |
| */ |
| static EFI_STATUS EFIAPI |
| efi_snp_get_driver_name ( EFI_COMPONENT_NAME2_PROTOCOL *name2, |
| CHAR8 *language __unused, CHAR16 **driver_name ) { |
| struct efi_snp_device *snpdev = |
| container_of ( name2, struct efi_snp_device, name2 ); |
| |
| *driver_name = snpdev->driver_name; |
| return 0; |
| } |
| |
| /** |
| * Look up controller name |
| * |
| * @v name2 Component name protocol |
| * @v device Device |
| * @v child Child device, or NULL |
| * @v language Language to use |
| * @v driver_name Device name to fill in |
| * @ret efirc EFI status code |
| */ |
| static EFI_STATUS EFIAPI |
| efi_snp_get_controller_name ( EFI_COMPONENT_NAME2_PROTOCOL *name2, |
| EFI_HANDLE device __unused, |
| EFI_HANDLE child __unused, |
| CHAR8 *language __unused, |
| CHAR16 **controller_name ) { |
| struct efi_snp_device *snpdev = |
| container_of ( name2, struct efi_snp_device, name2 ); |
| |
| *controller_name = snpdev->controller_name; |
| return 0; |
| } |
| |
| /****************************************************************************** |
| * |
| * Load file protocol |
| * |
| ****************************************************************************** |
| */ |
| |
| /** |
| * Load file |
| * |
| * @v loadfile Load file protocol |
| * @v path File path |
| * @v booting Loading as part of a boot attempt |
| * @ret efirc EFI status code |
| */ |
| static EFI_STATUS EFIAPI |
| efi_snp_load_file ( EFI_LOAD_FILE_PROTOCOL *load_file, |
| EFI_DEVICE_PATH_PROTOCOL *path __unused, |
| BOOLEAN booting, UINTN *len __unused, |
| VOID *data __unused ) { |
| struct efi_snp_device *snpdev = |
| container_of ( load_file, struct efi_snp_device, load_file ); |
| struct net_device *netdev = snpdev->netdev; |
| int rc; |
| |
| /* Fail unless this is a boot attempt */ |
| if ( ! booting ) { |
| DBGC ( snpdev, "SNPDEV %p cannot load non-boot file\n", |
| snpdev ); |
| return EFI_UNSUPPORTED; |
| } |
| |
| /* Claim network devices for use by iPXE */ |
| efi_snp_claim(); |
| |
| /* Start watchdog holdoff timer */ |
| efi_watchdog_start(); |
| |
| /* Boot from network device */ |
| if ( ( rc = ipxe ( netdev ) ) != 0 ) |
| goto err_ipxe; |
| |
| /* Reset console */ |
| console_reset(); |
| |
| err_ipxe: |
| efi_watchdog_stop(); |
| efi_snp_release(); |
| return EFIRC ( rc ); |
| } |
| |
| /** Load file protocol */ |
| static EFI_LOAD_FILE_PROTOCOL efi_snp_load_file_protocol = { |
| .LoadFile = efi_snp_load_file, |
| }; |
| |
| /****************************************************************************** |
| * |
| * iPXE network driver |
| * |
| ****************************************************************************** |
| */ |
| |
| /** |
| * Locate SNP device corresponding to network device |
| * |
| * @v netdev Network device |
| * @ret snp SNP device, or NULL if not found |
| */ |
| static struct efi_snp_device * efi_snp_demux ( struct net_device *netdev ) { |
| struct efi_snp_device *snpdev; |
| |
| list_for_each_entry ( snpdev, &efi_snp_devices, list ) { |
| if ( snpdev->netdev == netdev ) |
| return snpdev; |
| } |
| return NULL; |
| } |
| |
| /** |
| * Create SNP device |
| * |
| * @v netdev Network device |
| * @v priv Private data |
| * @ret rc Return status code |
| */ |
| static int efi_snp_probe ( struct net_device *netdev, void *priv __unused ) { |
| EFI_BOOT_SERVICES *bs = efi_systab->BootServices; |
| struct efi_device *efidev; |
| struct efi_snp_device *snpdev; |
| unsigned int ifcnt; |
| void *interface; |
| unsigned int tci; |
| char vlan_name[ 12 /* ", VLAN xxxx" + NUL */ ]; |
| int leak = 0; |
| EFI_STATUS efirc; |
| int rc; |
| |
| /* Find parent EFI device */ |
| efidev = efidev_parent ( netdev->dev ); |
| if ( ! efidev ) { |
| DBG ( "SNP skipping non-EFI device %s\n", netdev->name ); |
| rc = 0; |
| goto err_no_efidev; |
| } |
| |
| /* Allocate the SNP device */ |
| snpdev = zalloc ( sizeof ( *snpdev ) ); |
| if ( ! snpdev ) { |
| rc = -ENOMEM; |
| goto err_alloc_snp; |
| } |
| snpdev->netdev = netdev_get ( netdev ); |
| snpdev->efidev = efidev; |
| INIT_LIST_HEAD ( &snpdev->rx ); |
| |
| /* Sanity check */ |
| if ( netdev->ll_protocol->ll_addr_len > sizeof ( EFI_MAC_ADDRESS ) ) { |
| DBGC ( snpdev, "SNPDEV %p cannot support link-layer address " |
| "length %d for %s\n", snpdev, |
| netdev->ll_protocol->ll_addr_len, netdev->name ); |
| rc = -ENOTSUP; |
| goto err_ll_addr_len; |
| } |
| |
| /* Populate the SNP structure */ |
| memcpy ( &snpdev->snp, &efi_snp_device_snp, sizeof ( snpdev->snp ) ); |
| snpdev->snp.Mode = &snpdev->mode; |
| if ( ( efirc = bs->CreateEvent ( EVT_NOTIFY_WAIT, TPL_NOTIFY, |
| efi_snp_wait_for_packet, snpdev, |
| &snpdev->snp.WaitForPacket ) ) != 0 ){ |
| rc = -EEFI ( efirc ); |
| DBGC ( snpdev, "SNPDEV %p could not create event: %s\n", |
| snpdev, strerror ( rc ) ); |
| goto err_create_event; |
| } |
| |
| /* Populate the SNP mode structure */ |
| snpdev->mode.State = EfiSimpleNetworkStopped; |
| efi_snp_set_mode ( snpdev ); |
| |
| /* Populate the NII structure */ |
| memcpy ( &snpdev->nii, &efi_snp_device_nii, sizeof ( snpdev->nii ) ); |
| snpdev->nii.Id = ( ( intptr_t ) &efi_snp_undi ); |
| snpdev->nii.IfNum = efi_undi_ifnum ( snpdev ); |
| efi_snp_undi.EntryPoint = ( ( intptr_t ) efi_undi_issue ); |
| ifcnt = ( ( efi_snp_undi.IFcntExt << 8 ) | efi_snp_undi.IFcnt ); |
| if ( ifcnt < snpdev->nii.IfNum ) |
| ifcnt = snpdev->nii.IfNum; |
| efi_snp_undi.IFcnt = ( ifcnt & 0xff ); |
| efi_snp_undi.IFcntExt = ( ifcnt >> 8 ); |
| efi_snp_undi.Fudge -= efi_undi_checksum ( &efi_snp_undi, |
| sizeof ( efi_snp_undi ) ); |
| |
| /* Populate the VLAN configuration protocol */ |
| memcpy ( &snpdev->vcfg, &efi_vlan, sizeof ( snpdev->vcfg ) ); |
| |
| /* Populate the component name structure */ |
| efi_snprintf ( snpdev->driver_name, |
| ( sizeof ( snpdev->driver_name ) / |
| sizeof ( snpdev->driver_name[0] ) ), |
| "%s %s", product_short_name, netdev->dev->driver_name ); |
| tci = vlan_tci ( netdev ); |
| if ( tci ) { |
| snprintf ( vlan_name, sizeof ( vlan_name ), ", VLAN %d", |
| VLAN_TAG ( tci ) ); |
| } else { |
| vlan_name[0] = '\0'; |
| } |
| efi_snprintf ( snpdev->controller_name, |
| ( sizeof ( snpdev->controller_name ) / |
| sizeof ( snpdev->controller_name[0] ) ), |
| "%s %s (%s, %s%s)", product_short_name, |
| netdev->dev->driver_name, netdev->dev->name, |
| netdev_addr ( netdev ), vlan_name ); |
| snpdev->name2.GetDriverName = efi_snp_get_driver_name; |
| snpdev->name2.GetControllerName = efi_snp_get_controller_name; |
| snpdev->name2.SupportedLanguages = "en"; |
| |
| /* Populate the load file protocol structure */ |
| memcpy ( &snpdev->load_file, &efi_snp_load_file_protocol, |
| sizeof ( snpdev->load_file ) ); |
| |
| /* Populate the device name */ |
| efi_snprintf ( snpdev->name, ( sizeof ( snpdev->name ) / |
| sizeof ( snpdev->name[0] ) ), |
| "%s", netdev->name ); |
| |
| /* Construct device path */ |
| snpdev->path = efi_netdev_path ( netdev ); |
| if ( ! snpdev->path ) { |
| rc = -ENOMEM; |
| goto err_path; |
| } |
| |
| /* Install the SNP */ |
| if ( ( efirc = bs->InstallMultipleProtocolInterfaces ( |
| &snpdev->handle, |
| &efi_simple_network_protocol_guid, &snpdev->snp, |
| &efi_device_path_protocol_guid, snpdev->path, |
| &efi_nii_protocol_guid, &snpdev->nii, |
| &efi_nii31_protocol_guid, &snpdev->nii, |
| &efi_vlan_config_protocol_guid, &snpdev->vcfg, |
| &efi_component_name2_protocol_guid, &snpdev->name2, |
| &efi_load_file_protocol_guid, &snpdev->load_file, |
| NULL ) ) != 0 ) { |
| rc = -EEFI ( efirc ); |
| DBGC ( snpdev, "SNPDEV %p could not install protocols: %s\n", |
| snpdev, strerror ( rc ) ); |
| goto err_install_protocol_interface; |
| } |
| |
| /* SnpDxe will repeatedly start up and shut down our NII/UNDI |
| * interface (in order to obtain the MAC address) before |
| * discovering that it cannot install another SNP on the same |
| * handle. This causes the underlying network device to be |
| * unexpectedly closed. |
| * |
| * Prevent this by opening our own NII (and NII31) protocol |
| * instances to prevent SnpDxe from attempting to bind to |
| * them. |
| */ |
| if ( ( efirc = bs->OpenProtocol ( snpdev->handle, |
| &efi_nii_protocol_guid, &interface, |
| efi_image_handle, snpdev->handle, |
| ( EFI_OPEN_PROTOCOL_BY_DRIVER | |
| EFI_OPEN_PROTOCOL_EXCLUSIVE )))!=0){ |
| rc = -EEFI ( efirc ); |
| DBGC ( snpdev, "SNPDEV %p could not open NII protocol: %s\n", |
| snpdev, strerror ( rc ) ); |
| goto err_open_nii; |
| } |
| if ( ( efirc = bs->OpenProtocol ( snpdev->handle, |
| &efi_nii31_protocol_guid, &interface, |
| efi_image_handle, snpdev->handle, |
| ( EFI_OPEN_PROTOCOL_BY_DRIVER | |
| EFI_OPEN_PROTOCOL_EXCLUSIVE )))!=0){ |
| rc = -EEFI ( efirc ); |
| DBGC ( snpdev, "SNPDEV %p could not open NII31 protocol: %s\n", |
| snpdev, strerror ( rc ) ); |
| goto err_open_nii31; |
| } |
| |
| /* Add as child of EFI parent device */ |
| if ( ( rc = efi_child_add ( efidev->device, snpdev->handle ) ) != 0 ) { |
| DBGC ( snpdev, "SNPDEV %p could not become child of %s: %s\n", |
| snpdev, efi_handle_name ( efidev->device ), |
| strerror ( rc ) ); |
| goto err_efi_child_add; |
| } |
| |
| /* Install HII */ |
| if ( ( rc = efi_snp_hii_install ( snpdev ) ) != 0 ) { |
| DBGC ( snpdev, "SNPDEV %p could not install HII: %s\n", |
| snpdev, strerror ( rc ) ); |
| /* HII fails on several platforms. It's |
| * non-essential, so treat this as a non-fatal |
| * error. |
| */ |
| } |
| |
| /* Add to list of SNP devices */ |
| list_add ( &snpdev->list, &efi_snp_devices ); |
| |
| /* Close device path */ |
| bs->CloseProtocol ( efidev->device, &efi_device_path_protocol_guid, |
| efi_image_handle, efidev->device ); |
| |
| DBGC ( snpdev, "SNPDEV %p installed for %s as device %s\n", |
| snpdev, netdev->name, efi_handle_name ( snpdev->handle ) ); |
| return 0; |
| |
| list_del ( &snpdev->list ); |
| if ( snpdev->package_list ) |
| leak |= efi_snp_hii_uninstall ( snpdev ); |
| efi_child_del ( efidev->device, snpdev->handle ); |
| err_efi_child_add: |
| bs->CloseProtocol ( snpdev->handle, &efi_nii31_protocol_guid, |
| efi_image_handle, snpdev->handle ); |
| err_open_nii31: |
| bs->CloseProtocol ( snpdev->handle, &efi_nii_protocol_guid, |
| efi_image_handle, snpdev->handle ); |
| err_open_nii: |
| if ( ( efirc = bs->UninstallMultipleProtocolInterfaces ( |
| snpdev->handle, |
| &efi_simple_network_protocol_guid, &snpdev->snp, |
| &efi_device_path_protocol_guid, snpdev->path, |
| &efi_nii_protocol_guid, &snpdev->nii, |
| &efi_nii31_protocol_guid, &snpdev->nii, |
| &efi_vlan_config_protocol_guid, &snpdev->vcfg, |
| &efi_component_name2_protocol_guid, &snpdev->name2, |
| &efi_load_file_protocol_guid, &snpdev->load_file, |
| NULL ) ) != 0 ) { |
| DBGC ( snpdev, "SNPDEV %p could not uninstall: %s\n", |
| snpdev, strerror ( -EEFI ( efirc ) ) ); |
| leak = 1; |
| } |
| efi_nullify_snp ( &snpdev->snp ); |
| efi_nullify_nii ( &snpdev->nii ); |
| efi_nullify_vlan ( &snpdev->vcfg ); |
| efi_nullify_name2 ( &snpdev->name2 ); |
| efi_nullify_load_file ( &snpdev->load_file ); |
| err_install_protocol_interface: |
| if ( ! leak ) |
| free ( snpdev->path ); |
| err_path: |
| bs->CloseEvent ( snpdev->snp.WaitForPacket ); |
| err_create_event: |
| err_ll_addr_len: |
| if ( ! leak ) { |
| netdev_put ( netdev ); |
| free ( snpdev ); |
| } |
| err_alloc_snp: |
| err_no_efidev: |
| if ( leak ) |
| DBGC ( snpdev, "SNPDEV %p nullified and leaked\n", snpdev ); |
| return rc; |
| } |
| |
| /** |
| * Handle SNP device or link state change |
| * |
| * @v netdev Network device |
| * @v priv Private data |
| */ |
| static void efi_snp_notify ( struct net_device *netdev, void *priv __unused ) { |
| struct efi_snp_device *snpdev; |
| |
| /* Locate SNP device */ |
| snpdev = efi_snp_demux ( netdev ); |
| if ( ! snpdev ) { |
| DBG ( "SNP skipping non-SNP device %s\n", netdev->name ); |
| return; |
| } |
| |
| /* Update link state */ |
| snpdev->mode.MediaPresent = |
| ( netdev_link_ok ( netdev ) ? TRUE : FALSE ); |
| DBGC ( snpdev, "SNPDEV %p link is %s\n", snpdev, |
| ( snpdev->mode.MediaPresent ? "up" : "down" ) ); |
| |
| /* Update mode state */ |
| efi_snp_set_state ( snpdev ); |
| } |
| |
| /** |
| * Destroy SNP device |
| * |
| * @v netdev Network device |
| * @v priv Private data |
| */ |
| static void efi_snp_remove ( struct net_device *netdev, void *priv __unused ) { |
| EFI_BOOT_SERVICES *bs = efi_systab->BootServices; |
| struct efi_snp_device *snpdev; |
| int leak = efi_shutdown_in_progress; |
| EFI_STATUS efirc; |
| |
| /* Locate SNP device */ |
| snpdev = efi_snp_demux ( netdev ); |
| if ( ! snpdev ) { |
| DBG ( "SNP skipping non-SNP device %s\n", netdev->name ); |
| return; |
| } |
| |
| /* Uninstall the SNP */ |
| list_del ( &snpdev->list ); |
| if ( snpdev->package_list ) |
| leak |= efi_snp_hii_uninstall ( snpdev ); |
| efi_child_del ( snpdev->efidev->device, snpdev->handle ); |
| bs->CloseProtocol ( snpdev->handle, &efi_nii_protocol_guid, |
| efi_image_handle, snpdev->handle ); |
| bs->CloseProtocol ( snpdev->handle, &efi_nii31_protocol_guid, |
| efi_image_handle, snpdev->handle ); |
| if ( ( ! efi_shutdown_in_progress ) && |
| ( ( efirc = bs->UninstallMultipleProtocolInterfaces ( |
| snpdev->handle, |
| &efi_simple_network_protocol_guid, &snpdev->snp, |
| &efi_device_path_protocol_guid, snpdev->path, |
| &efi_nii_protocol_guid, &snpdev->nii, |
| &efi_nii31_protocol_guid, &snpdev->nii, |
| &efi_vlan_config_protocol_guid, &snpdev->vcfg, |
| &efi_component_name2_protocol_guid, &snpdev->name2, |
| &efi_load_file_protocol_guid, &snpdev->load_file, |
| NULL ) ) != 0 ) ) { |
| DBGC ( snpdev, "SNPDEV %p could not uninstall: %s\n", |
| snpdev, strerror ( -EEFI ( efirc ) ) ); |
| leak = 1; |
| } |
| efi_nullify_snp ( &snpdev->snp ); |
| efi_nullify_nii ( &snpdev->nii ); |
| efi_nullify_vlan ( &snpdev->vcfg ); |
| efi_nullify_name2 ( &snpdev->name2 ); |
| efi_nullify_load_file ( &snpdev->load_file ); |
| if ( ! leak ) |
| free ( snpdev->path ); |
| bs->CloseEvent ( snpdev->snp.WaitForPacket ); |
| if ( ! leak ) { |
| netdev_put ( snpdev->netdev ); |
| free ( snpdev ); |
| } |
| |
| /* Report leakage, if applicable */ |
| if ( leak && ( ! efi_shutdown_in_progress ) ) |
| DBGC ( snpdev, "SNPDEV %p nullified and leaked\n", snpdev ); |
| } |
| |
| /** SNP driver */ |
| struct net_driver efi_snp_driver __net_driver = { |
| .name = "SNP", |
| .probe = efi_snp_probe, |
| .notify = efi_snp_notify, |
| .remove = efi_snp_remove, |
| }; |
| |
| /** |
| * Find SNP device by EFI device handle |
| * |
| * @v handle EFI device handle |
| * @ret snpdev SNP device, or NULL |
| */ |
| struct efi_snp_device * find_snpdev ( EFI_HANDLE handle ) { |
| struct efi_snp_device *snpdev; |
| |
| list_for_each_entry ( snpdev, &efi_snp_devices, list ) { |
| if ( snpdev->handle == handle ) |
| return snpdev; |
| } |
| return NULL; |
| } |
| |
| /** |
| * Get most recently opened SNP device |
| * |
| * @ret snpdev Most recently opened SNP device, or NULL |
| */ |
| struct efi_snp_device * last_opened_snpdev ( void ) { |
| struct net_device *netdev; |
| |
| netdev = last_opened_netdev(); |
| if ( ! netdev ) |
| return NULL; |
| |
| return efi_snp_demux ( netdev ); |
| } |
| |
| /** |
| * Add to SNP claimed/released count |
| * |
| * @v delta Claim count change |
| */ |
| void efi_snp_add_claim ( int delta ) { |
| struct efi_snp_device *snpdev; |
| |
| /* Raise TPL if we are about to claim devices */ |
| if ( ! efi_snp_claimed ) |
| efi_raise_tpl ( &efi_snp_saved_tpl ); |
| |
| /* Claim SNP devices */ |
| efi_snp_claimed += delta; |
| assert ( efi_snp_claimed >= 0 ); |
| |
| /* Update SNP mode state for each interface */ |
| list_for_each_entry ( snpdev, &efi_snp_devices, list ) |
| efi_snp_set_state ( snpdev ); |
| |
| /* Restore TPL if we have released devices */ |
| if ( ! efi_snp_claimed ) |
| efi_restore_tpl ( &efi_snp_saved_tpl ); |
| } |