blob: 8427b6ce3275d8edb2b0c9ee9df50c1357593787 [file] [log] [blame]
/*
* Copyright (C) 2014 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 <stdio.h>
#include <string.h>
#include <errno.h>
#include <ipxe/iobuf.h>
#include <ipxe/netdevice.h>
#include <ipxe/ethernet.h>
#include <ipxe/if_ether.h>
#include <ipxe/vsprintf.h>
#include <ipxe/timer.h>
#include <ipxe/efi/efi.h>
#include <ipxe/efi/Protocol/SimpleNetwork.h>
#include <ipxe/efi/efi_driver.h>
#include <ipxe/efi/efi_utils.h>
#include <ipxe/efi/efi_snp.h>
#include "snpnet.h"
/** @file
*
* SNP NIC driver
*
*/
/** An SNP NIC */
struct snp_nic {
/** EFI device */
struct efi_device *efidev;
/** Simple network protocol */
EFI_SIMPLE_NETWORK_PROTOCOL *snp;
/** Generic device */
struct device dev;
/** Maximum packet size
*
* This is calculated as the sum of MediaHeaderSize and
* MaxPacketSize, and may therefore be an overestimate.
*/
size_t mtu;
/** Current transmit buffer */
struct io_buffer *txbuf;
/** Current receive buffer */
struct io_buffer *rxbuf;
};
/** Maximum number of received packets per poll */
#define SNP_RX_QUOTA 4
/** Maximum initialisation retry count */
#define SNP_INITIALIZE_RETRY_MAX 10
/** Delay between each initialisation retry */
#define SNP_INITIALIZE_RETRY_DELAY_MS 10
/** Additional padding for receive buffers
*
* Some SNP implementations seem to require additional space in the
* allocated receive buffers, otherwise full-length packets will be
* silently dropped.
*
* The EDK2 MnpDxe driver happens to allocate an additional 8 bytes of
* padding (4 for a VLAN tag, 4 for the Ethernet frame checksum).
* Match this behaviour since drivers are very likely to have been
* tested against MnpDxe.
*/
#define SNP_RX_PAD 8
/** An SNP interface patch to inhibit shutdown for insomniac devices */
struct snp_insomniac_patch {
/** Original Shutdown() method */
EFI_SIMPLE_NETWORK_SHUTDOWN shutdown;
/** Original Stop() method */
EFI_SIMPLE_NETWORK_STOP stop;
};
/**
* Format SNP MAC address (for debugging)
*
* @v mac MAC address
* @v len Length of MAC address
* @ret text MAC address as text
*/
static const char * snpnet_mac_text ( EFI_MAC_ADDRESS *mac, size_t len ) {
static char buf[ sizeof ( *mac ) * 3 /* "xx:" or "xx\0" */ ];
size_t used = 0;
unsigned int i;
for ( i = 0 ; i < len ; i++ ) {
used += ssnprintf ( &buf[used], ( sizeof ( buf ) - used ),
"%s%02x", ( used ? ":" : "" ),
mac->Addr[i] );
}
return buf;
}
/**
* Dump SNP mode information (for debugging)
*
* @v netdev Network device
*/
static void snpnet_dump_mode ( struct net_device *netdev ) {
struct snp_nic *snp = netdev->priv;
EFI_SIMPLE_NETWORK_MODE *mode = snp->snp->Mode;
size_t mac_len = mode->HwAddressSize;
unsigned int i;
/* Do nothing unless debugging is enabled */
if ( ! DBG_EXTRA )
return;
DBGC2 ( snp, "SNP %s st %d type %d hdr %d pkt %d rxflt %#x/%#x%s "
"nvram %d acc %d mcast %d/%d\n", netdev->name, mode->State,
mode->IfType, mode->MediaHeaderSize, mode->MaxPacketSize,
mode->ReceiveFilterSetting, mode->ReceiveFilterMask,
( mode->MultipleTxSupported ? " multitx" : "" ),
mode->NvRamSize, mode->NvRamAccessSize,
mode->MCastFilterCount, mode->MaxMCastFilterCount );
DBGC2 ( snp, "SNP %s hw %s", netdev->name,
snpnet_mac_text ( &mode->PermanentAddress, mac_len ) );
DBGC2 ( snp, " addr %s%s",
snpnet_mac_text ( &mode->CurrentAddress, mac_len ),
( mode->MacAddressChangeable ? "" : "(f)" ) );
DBGC2 ( snp, " bcast %s\n",
snpnet_mac_text ( &mode->BroadcastAddress, mac_len ) );
for ( i = 0 ; i < mode->MCastFilterCount ; i++ ) {
DBGC2 ( snp, "SNP %s mcast %s\n", netdev->name,
snpnet_mac_text ( &mode->MCastFilter[i], mac_len ) );
}
DBGC2 ( snp, "SNP %s media %s\n", netdev->name,
( mode->MediaPresentSupported ?
( mode->MediaPresent ? "present" : "not present" ) :
"presence not supported" ) );
}
/**
* Check link state
*
* @v netdev Network device
*/
static void snpnet_check_link ( struct net_device *netdev ) {
struct snp_nic *snp = netdev->priv;
EFI_SIMPLE_NETWORK_MODE *mode = snp->snp->Mode;
/* Do nothing unless media presence detection is supported */
if ( ! mode->MediaPresentSupported )
return;
/* Report any link status change */
if ( mode->MediaPresent && ( ! netdev_link_ok ( netdev ) ) ) {
netdev_link_up ( netdev );
} else if ( ( ! mode->MediaPresent ) && netdev_link_ok ( netdev ) ) {
netdev_link_down ( netdev );
}
}
/**
* Transmit packet
*
* @v netdev Network device
* @v iobuf I/O buffer
* @ret rc Return status code
*/
static int snpnet_transmit ( struct net_device *netdev,
struct io_buffer *iobuf ) {
struct snp_nic *snp = netdev->priv;
EFI_STATUS efirc;
int rc;
/* Do nothing if shutdown is in progress */
if ( efi_shutdown_in_progress )
return -ECANCELED;
/* Defer the packet if there is already a transmission in progress */
if ( snp->txbuf ) {
netdev_tx_defer ( netdev, iobuf );
return 0;
}
/* Pad to minimum Ethernet length, to work around underlying
* drivers that do not correctly handle frame padding
* themselves.
*/
iob_pad ( iobuf, ETH_ZLEN );
/* Transmit packet */
if ( ( efirc = snp->snp->Transmit ( snp->snp, 0, iob_len ( iobuf ),
iobuf->data, NULL, NULL,
NULL ) ) != 0 ) {
rc = -EEFI ( efirc );
DBGC ( snp, "SNP %s could not transmit: %s\n",
netdev->name, strerror ( rc ) );
return rc;
}
snp->txbuf = iobuf;
return 0;
}
/**
* Poll for completed packets
*
* @v netdev Network device
*/
static void snpnet_poll_tx ( struct net_device *netdev ) {
struct snp_nic *snp = netdev->priv;
struct io_buffer *iobuf;
UINT32 irq;
VOID *txbuf;
EFI_STATUS efirc;
int rc;
/* Get status */
txbuf = NULL;
if ( ( efirc = snp->snp->GetStatus ( snp->snp, &irq, &txbuf ) ) != 0 ) {
rc = -EEFI ( efirc );
DBGC ( snp, "SNP %s could not get status: %s\n",
netdev->name, strerror ( rc ) );
netdev_rx_err ( netdev, NULL, rc );
return;
}
/* Do nothing unless we have a completion */
if ( ! txbuf )
return;
/* Sanity check */
if ( ! snp->txbuf ) {
DBGC ( snp, "SNP %s reported spurious TX completion\n",
netdev->name );
netdev_tx_err ( netdev, NULL, -EPIPE );
return;
}
/* Complete transmission */
iobuf = snp->txbuf;
snp->txbuf = NULL;
netdev_tx_complete ( netdev, iobuf );
}
/**
* Poll for received packets
*
* @v netdev Network device
*/
static void snpnet_poll_rx ( struct net_device *netdev ) {
struct snp_nic *snp = netdev->priv;
UINTN len;
unsigned int quota;
EFI_STATUS efirc;
int rc;
/* Retrieve up to SNP_RX_QUOTA packets */
for ( quota = SNP_RX_QUOTA ; quota ; quota-- ) {
/* Allocate buffer, if required */
if ( ! snp->rxbuf ) {
snp->rxbuf = alloc_iob ( snp->mtu + SNP_RX_PAD );
if ( ! snp->rxbuf ) {
/* Leave for next poll */
break;
}
}
/* Receive packet */
len = iob_tailroom ( snp->rxbuf );
if ( ( efirc = snp->snp->Receive ( snp->snp, NULL, &len,
snp->rxbuf->data, NULL,
NULL, NULL ) ) != 0 ) {
/* EFI_NOT_READY is just the usual "no packet"
* status indication; ignore it.
*/
if ( efirc == EFI_NOT_READY )
break;
/* Anything else is an error */
rc = -EEFI ( efirc );
DBGC ( snp, "SNP %s could not receive: %s\n",
netdev->name, strerror ( rc ) );
netdev_rx_err ( netdev, NULL, rc );
break;
}
/* Hand off to network stack */
iob_put ( snp->rxbuf, len );
netdev_rx ( netdev, snp->rxbuf );
snp->rxbuf = NULL;
}
}
/**
* Poll for completed packets
*
* @v netdev Network device
*/
static void snpnet_poll ( struct net_device *netdev ) {
/* Do nothing if shutdown is in progress */
if ( efi_shutdown_in_progress )
return;
/* Process any TX completions */
snpnet_poll_tx ( netdev );
/* Process any RX completions */
snpnet_poll_rx ( netdev );
/* Check for link state changes */
snpnet_check_link ( netdev );
}
/**
* Set receive filters
*
* @v netdev Network device
* @ret rc Return status code
*/
static int snpnet_rx_filters ( struct net_device *netdev ) {
struct snp_nic *snp = netdev->priv;
UINT32 filters[] = {
snp->snp->Mode->ReceiveFilterMask,
( EFI_SIMPLE_NETWORK_RECEIVE_UNICAST |
EFI_SIMPLE_NETWORK_RECEIVE_MULTICAST |
EFI_SIMPLE_NETWORK_RECEIVE_BROADCAST ),
( EFI_SIMPLE_NETWORK_RECEIVE_UNICAST |
EFI_SIMPLE_NETWORK_RECEIVE_PROMISCUOUS_MULTICAST |
EFI_SIMPLE_NETWORK_RECEIVE_BROADCAST ),
( EFI_SIMPLE_NETWORK_RECEIVE_UNICAST |
EFI_SIMPLE_NETWORK_RECEIVE_BROADCAST ),
( EFI_SIMPLE_NETWORK_RECEIVE_UNICAST ),
};
unsigned int i;
EFI_STATUS efirc;
int rc;
/* Try possible receive filters in turn */
for ( i = 0; i < ( sizeof ( filters ) / sizeof ( filters[0] ) ); i++ ) {
efirc = snp->snp->ReceiveFilters ( snp->snp, filters[i],
EFI_SIMPLE_NETWORK_RECEIVE_MULTICAST, TRUE,
0, NULL );
if ( efirc == 0 )
return 0;
rc = -EEFI ( efirc );
DBGC ( snp, "SNP %s could not set receive filters %#02x (have "
"%#02x): %s\n", netdev->name, filters[i],
snp->snp->Mode->ReceiveFilterSetting, strerror ( rc ) );
}
return rc;
}
/**
* Open network device
*
* @v netdev Network device
* @ret rc Return status code
*/
static int snpnet_open ( struct net_device *netdev ) {
struct snp_nic *snp = netdev->priv;
EFI_MAC_ADDRESS *mac = ( ( void * ) netdev->ll_addr );
EFI_SIMPLE_NETWORK_MODE *mode = snp->snp->Mode;
EFI_STATUS efirc;
unsigned int retry;
int rc;
/* Try setting MAC address (before initialising) */
if ( ( efirc = snp->snp->StationAddress ( snp->snp, FALSE, mac ) ) !=0){
rc = -EEFI ( efirc );
DBGC ( snp, "SNP %s could not set station address before "
"initialising: %s\n", netdev->name, strerror ( rc ) );
/* Ignore error */
}
/* Initialise NIC, retrying multiple times if link stays down */
for ( retry = 0 ; ; ) {
/* Initialise NIC, if not already initialised */
if ( ( mode->State != EfiSimpleNetworkInitialized ) &&
( ( efirc = snp->snp->Initialize ( snp->snp,
0, 0 ) ) != 0 ) ) {
rc = -EEFI ( efirc );
snpnet_dump_mode ( netdev );
DBGC ( snp, "SNP %s could not initialise: %s\n",
netdev->name, strerror ( rc ) );
return rc;
}
/* Stop if we have link up (or no link detection capability) */
if ( ( ! mode->MediaPresentSupported ) || mode->MediaPresent )
break;
/* Stop if we have exceeded our retry count. This is
* not a failure; it is plausible that we genuinely do
* not have link up.
*/
if ( ++retry >= SNP_INITIALIZE_RETRY_MAX )
break;
DBGC ( snp, "SNP %s retrying initialisation (retry %d)\n",
netdev->name, retry );
/* Delay to allow time for link to establish */
mdelay ( SNP_INITIALIZE_RETRY_DELAY_MS );
/* Shut down and retry (unless device is insomniac);
* this is sometimes necessary in order to persuade
* the underlying SNP driver to actually update the
* link state.
*/
if ( ( ! netdev_insomniac ( netdev ) ) &&
( ( efirc = snp->snp->Shutdown ( snp->snp ) ) != 0 ) ) {
rc = -EEFI ( efirc );
snpnet_dump_mode ( netdev );
DBGC ( snp, "SNP %s could not shut down: %s\n",
netdev->name, strerror ( rc ) );
return rc;
}
}
/* Try setting MAC address (after initialising) */
if ( ( efirc = snp->snp->StationAddress ( snp->snp, FALSE, mac ) ) !=0){
rc = -EEFI ( efirc );
DBGC ( snp, "SNP %s could not set station address after "
"initialising: %s\n", netdev->name, strerror ( rc ) );
/* Ignore error */
}
/* Set receive filters */
if ( ( rc = snpnet_rx_filters ( netdev ) ) != 0 ) {
/* Ignore error */
}
/* Dump mode information (for debugging) */
snpnet_dump_mode ( netdev );
return 0;
}
/**
* Close network device
*
* @v netdev Network device
*/
static void snpnet_close ( struct net_device *netdev ) {
struct snp_nic *snp = netdev->priv;
EFI_STATUS efirc;
int rc;
/* Shut down NIC (unless whole system shutdown is in progress,
* or device is insomniac).
*/
if ( ( ! efi_shutdown_in_progress ) &&
( ! netdev_insomniac ( netdev ) ) &&
( ( efirc = snp->snp->Shutdown ( snp->snp ) ) != 0 ) ) {
rc = -EEFI ( efirc );
DBGC ( snp, "SNP %s could not shut down: %s\n",
netdev->name, strerror ( rc ) );
/* Nothing we can do about this */
}
/* Discard transmit buffer, if applicable */
if ( snp->txbuf ) {
netdev_tx_complete_err ( netdev, snp->txbuf, -ECANCELED );
snp->txbuf = NULL;
}
/* Discard receive buffer, if applicable */
if ( snp->rxbuf ) {
free_iob ( snp->rxbuf );
snp->rxbuf = NULL;
}
}
/** SNP network device operations */
static struct net_device_operations snpnet_operations = {
.open = snpnet_open,
.close = snpnet_close,
.transmit = snpnet_transmit,
.poll = snpnet_poll,
};
/**
* Check to see if driver supports a device
*
* @v device EFI device handle
* @v protocol Protocol GUID
* @v inhibit_wifi Inhibit wireless devices
* @ret rc Return status code
*/
int snpnet_supported ( EFI_HANDLE device, EFI_GUID *protocol,
int inhibit_wifi ) {
EFI_HANDLE parent;
int rc;
/* Check that this is not a device we are providing ourselves */
if ( find_snpdev ( device ) != NULL ) {
DBGCP ( device, "HANDLE %s is provided by this binary\n",
efi_handle_name ( device ) );
return -ENOTTY;
}
/* Test for presence of protocol */
if ( ( rc = efi_test ( device, protocol ) ) != 0 ) {
DBGCP ( device, "HANDLE %s is not a %s device\n",
efi_handle_name ( device ),
efi_guid_ntoa ( protocol ) );
return rc;
}
/* Check that there are no instances of this protocol further
* up this device path.
*/
if ( ( rc = efi_locate_device ( device, protocol,
&parent, 1 ) ) == 0 ) {
DBGC2 ( device, "HANDLE %s has %s-supporting parent ",
efi_handle_name ( device ),
efi_guid_ntoa ( protocol ) );
DBGC2 ( device, "%s\n", efi_handle_name ( parent ) );
return -ENOTTY;
}
DBGC ( device, "HANDLE %s is a %s device\n",
efi_handle_name ( device ), efi_guid_ntoa ( protocol ) );
/* Check for wireless devices, if applicable */
if ( inhibit_wifi &&
( ( efi_test ( device, &efi_wifi2_protocol_guid ) ) == 0 ) ) {
DBGC ( device, "HANDLE %s is wireless: assuming vendor %s "
"driver is too unreliable to use\n",
efi_handle_name ( device ),
efi_guid_ntoa ( protocol ) );
return -ENOTTY;
}
return 0;
}
/**
* Check if device must be insomniac
*
* @v device EFI device handle
* @v is_insomniac Device must be insomniac
*/
static int snpnet_is_insomniac ( EFI_HANDLE device ) {
int rc;
/* Check for wireless devices
*
* The UEFI model for wireless network configuration is
* somewhat underdefined. At the time of writing, the EDK2
* "UEFI WiFi Connection Manager" driver provides only one way
* to configure wireless network credentials, which is to
* enter them interactively via an HII form. Credentials are
* not stored (or exposed via any protocol interface), and so
* any temporary disconnection from the wireless network will
* inevitably leave the interface in an unusable state that
* cannot be recovered without user intervention.
*
* Experimentation shows that at least some wireless network
* drivers will disconnect from the wireless network when the
* SNP Shutdown() method is called, or if the device is not
* polled sufficiently frequently to maintain its association
* to the network. We therefore inhibit calls to Shutdown()
* and Stop() for any such SNP protocol interfaces, and mark
* our network device as insomniac so that it will be polled
* even when closed.
*/
if ( ( rc = efi_test ( device, &efi_wifi2_protocol_guid ) ) == 0 ) {
DBGC ( device, "SNP %s is wireless: assuming insomniac\n",
efi_handle_name ( device ) );
return 1;
}
return 0;
}
/**
* Ignore shutdown attempt
*
* @v snp SNP interface
* @ret efirc EFI status code
*/
static EFI_STATUS EFIAPI
snpnet_do_nothing ( EFI_SIMPLE_NETWORK_PROTOCOL *snp __unused ) {
return 0;
}
/**
* Patch SNP protocol interface to prevent shutdown
*
* @v device EFI device handle
* @v patch Interface patch
* @ret rc Return status code
*/
static int snpnet_insomniac_patch ( EFI_HANDLE device,
struct snp_insomniac_patch *patch ) {
EFI_SIMPLE_NETWORK_PROTOCOL *interface;
int rc;
/* Open interface for ephemeral use */
if ( ( rc = efi_open ( device, &efi_simple_network_protocol_guid,
&interface ) ) != 0 ) {
DBGC ( device, "SNP %s cannot open SNP protocol for patching: "
"%s\n", efi_handle_name ( device ), strerror ( rc ) );
return rc;
}
/* Record original Shutdown() and Stop() methods */
patch->shutdown = interface->Shutdown;
patch->stop = interface->Stop;
/* Inhibit other UEFI drivers' calls to Shutdown() and Stop()
*
* This is necessary since disconnecting the MnpDxe driver
* will attempt to shut down the SNP device, which would leave
* us with an unusable device.
*/
interface->Shutdown = snpnet_do_nothing;
interface->Stop = snpnet_do_nothing;
DBGC ( device, "SNP %s patched to inhibit shutdown\n",
efi_handle_name ( device ) );
return 0;
}
/**
* Restore patched SNP protocol interface
*
* @v device EFI device handle
* @v patch Interface patch to fill in
* @ret rc Return status code
*/
static int snpnet_insomniac_restore ( EFI_HANDLE device,
struct snp_insomniac_patch *patch ) {
EFI_SIMPLE_NETWORK_PROTOCOL *interface;
int rc;
/* Avoid returning uninitialised data on error */
memset ( patch, 0, sizeof ( *patch ) );
/* Open interface for ephemeral use */
if ( ( rc = efi_open ( device, &efi_simple_network_protocol_guid,
&interface ) ) != 0 ) {
DBGC ( device, "SNP %s cannot open patched SNP protocol: %s\n",
efi_handle_name ( device ), strerror ( rc ) );
return rc;
}
/* Restore original Shutdown() and Stop() methods, if possible */
if ( interface->Shutdown == snpnet_do_nothing )
interface->Shutdown = patch->shutdown;
if ( interface->Stop == snpnet_do_nothing )
interface->Stop = patch->stop;
/* Check that original methods were restored (by us or others) */
if ( ( interface->Shutdown != patch->shutdown ) ||
( interface->Stop != patch->stop ) ) {
DBGC ( device, "SNP %s could not restore patched SNP "
"protocol\n", efi_handle_name ( device ) );
return -EBUSY;
}
return 0;
}
/**
* Exclude existing drivers
*
* @v device EFI device handle
* @ret rc Return status code
*/
int snpnet_exclude ( EFI_HANDLE device ) {
EFI_GUID *protocol = &efi_simple_network_protocol_guid;
struct snp_insomniac_patch patch;
int insomniac;
int rc;
/* Check if this is a device that must not ever be shut down */
insomniac = snpnet_is_insomniac ( device );
/* Inhibit calls to Shutdown() and Stop(), if applicable */
if ( insomniac &&
( ( rc = snpnet_insomniac_patch ( device, &patch ) ) != 0 ) ) {
goto err_patch;
}
/* Exclude existing SNP drivers */
if ( ( rc = efi_driver_exclude ( device, protocol ) ) != 0 ) {
DBGC ( device, "SNP %s could not exclude drivers: %s\n",
efi_handle_name ( device ), strerror ( rc ) );
goto err_exclude;
}
err_exclude:
if ( insomniac )
snpnet_insomniac_restore ( device, &patch );
err_patch:
return rc;
}
/**
* Attach driver to device
*
* @v efidev EFI device
* @ret rc Return status code
*/
int snpnet_start ( struct efi_device *efidev ) {
EFI_HANDLE device = efidev->device;
EFI_SIMPLE_NETWORK_PROTOCOL *interface;
EFI_SIMPLE_NETWORK_MODE *mode;
struct net_device *netdev;
struct snp_nic *snp;
EFI_STATUS efirc;
int rc;
/* Open SNP protocol */
if ( ( rc = efi_open_by_driver ( device,
&efi_simple_network_protocol_guid,
&interface ) ) != 0 ) {
DBGC ( device, "SNP %s cannot open SNP protocol: %s\n",
efi_handle_name ( device ), strerror ( rc ) );
DBGC_EFI_OPENERS ( device, device,
&efi_simple_network_protocol_guid );
goto err_open_protocol;
}
/* Allocate and initialise structure */
netdev = alloc_etherdev ( sizeof ( *snp ) );
if ( ! netdev ) {
rc = -ENOMEM;
goto err_alloc;
}
netdev_init ( netdev, &snpnet_operations );
snp = netdev->priv;
snp->efidev = efidev;
snp->snp = interface;
mode = snp->snp->Mode;
efidev_set_drvdata ( efidev, netdev );
/* Populate underlying device information */
efi_device_info ( device, "SNP", &snp->dev );
snp->dev.driver_name = "SNP";
snp->dev.parent = &efidev->dev;
list_add ( &snp->dev.siblings, &efidev->dev.children );
INIT_LIST_HEAD ( &snp->dev.children );
netdev->dev = &snp->dev;
/* Check if device is insomniac */
if ( snpnet_is_insomniac ( device ) )
netdev->state |= NETDEV_INSOMNIAC;
/* Bring to the correct state for a closed interface */
if ( ( mode->State == EfiSimpleNetworkStopped ) &&
( ( efirc = snp->snp->Start ( snp->snp ) ) != 0 ) ) {
rc = -EEFI ( efirc );
DBGC ( device, "SNP %s could not start: %s\n",
efi_handle_name ( device ), strerror ( rc ) );
goto err_start;
}
if ( ( mode->State == EfiSimpleNetworkInitialized ) &&
( ! netdev_insomniac ( netdev ) ) &&
( ( efirc = snp->snp->Shutdown ( snp->snp ) ) != 0 ) ) {
rc = -EEFI ( efirc );
DBGC ( device, "SNP %s could not shut down: %s\n",
efi_handle_name ( device ), strerror ( rc ) );
goto err_shutdown;
}
/* Populate network device parameters */
if ( mode->HwAddressSize != netdev->ll_protocol->hw_addr_len ) {
DBGC ( device, "SNP %s has invalid hardware address length "
"%d\n", efi_handle_name ( device ), mode->HwAddressSize);
rc = -ENOTSUP;
goto err_hw_addr_len;
}
memcpy ( netdev->hw_addr, &mode->PermanentAddress,
netdev->ll_protocol->hw_addr_len );
if ( mode->HwAddressSize != netdev->ll_protocol->ll_addr_len ) {
DBGC ( device, "SNP %s has invalid link-layer address length "
"%d\n", efi_handle_name ( device ), mode->HwAddressSize);
rc = -ENOTSUP;
goto err_ll_addr_len;
}
memcpy ( netdev->ll_addr, &mode->CurrentAddress,
netdev->ll_protocol->ll_addr_len );
snp->mtu = ( snp->snp->Mode->MaxPacketSize +
snp->snp->Mode->MediaHeaderSize );
/* Register network device */
if ( ( rc = register_netdev ( netdev ) ) != 0 )
goto err_register_netdev;
DBGC ( device, "SNP %s registered as %s\n",
efi_handle_name ( device ), netdev->name );
/* Set initial link state */
if ( snp->snp->Mode->MediaPresentSupported ) {
snpnet_check_link ( netdev );
} else {
netdev_link_up ( netdev );
}
return 0;
unregister_netdev ( netdev );
err_register_netdev:
err_ll_addr_len:
err_hw_addr_len:
err_shutdown:
err_start:
list_del ( &snp->dev.siblings );
netdev_nullify ( netdev );
netdev_put ( netdev );
err_alloc:
efi_close_by_driver ( device, &efi_simple_network_protocol_guid );
err_open_protocol:
return rc;
}
/**
* Detach driver from device
*
* @v efidev EFI device
*/
void snpnet_stop ( struct efi_device *efidev ) {
struct net_device *netdev = efidev_get_drvdata ( efidev );
struct snp_nic *snp = netdev->priv;
EFI_HANDLE device = efidev->device;
EFI_STATUS efirc;
int rc;
/* Unregister network device */
unregister_netdev ( netdev );
/* Stop SNP protocol (unless whole system shutdown is in progress) */
if ( ( ! efi_shutdown_in_progress ) &&
( ( efirc = snp->snp->Stop ( snp->snp ) ) != 0 ) ) {
rc = -EEFI ( efirc );
DBGC ( device, "SNP %s could not stop: %s\n",
efi_handle_name ( device ), strerror ( rc ) );
/* Nothing we can do about this */
}
/* Free network device */
list_del ( &snp->dev.siblings );
netdev_nullify ( netdev );
netdev_put ( netdev );
/* Close SNP protocol */
efi_close_by_driver ( device, &efi_simple_network_protocol_guid );
}