aboutsummaryrefslogtreecommitdiffstats
path: root/roms/ipxe/src/drivers/net/efi/snpnet.c
diff options
context:
space:
mode:
Diffstat (limited to 'roms/ipxe/src/drivers/net/efi/snpnet.c')
-rw-r--r--roms/ipxe/src/drivers/net/efi/snpnet.c563
1 files changed, 563 insertions, 0 deletions
diff --git a/roms/ipxe/src/drivers/net/efi/snpnet.c b/roms/ipxe/src/drivers/net/efi/snpnet.c
new file mode 100644
index 00000000..96642c4c
--- /dev/null
+++ b/roms/ipxe/src/drivers/net/efi/snpnet.c
@@ -0,0 +1,563 @@
+/*
+ * 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/vsprintf.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 "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
+
+/**
+ * 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 ( netdev );
+ 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 ( netdev );
+ 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 ( netdev );
+ EFI_STATUS efirc;
+ int rc;
+
+ /* Defer the packet if there is already a transmission in progress */
+ if ( snp->txbuf ) {
+ netdev_tx_defer ( netdev, iobuf );
+ return 0;
+ }
+
+ /* 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 */
+ 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 );
+ 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 ) {
+
+ /* 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_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],
+ 0, 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_STATUS efirc;
+ 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 */
+ if ( ( 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;
+ }
+
+ /* 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 */
+ if ( ( 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,
+};
+
+/**
+ * Attach driver to device
+ *
+ * @v efidev EFI device
+ * @ret rc Return status code
+ */
+int snpnet_start ( struct efi_device *efidev ) {
+ EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+ EFI_HANDLE device = efidev->device;
+ EFI_SIMPLE_NETWORK_MODE *mode;
+ struct net_device *netdev;
+ struct snp_nic *snp;
+ void *interface;
+ EFI_STATUS efirc;
+ int rc;
+
+ /* Open SNP protocol */
+ if ( ( efirc = bs->OpenProtocol ( device,
+ &efi_simple_network_protocol_guid,
+ &interface, efi_image_handle, device,
+ ( EFI_OPEN_PROTOCOL_BY_DRIVER |
+ EFI_OPEN_PROTOCOL_EXCLUSIVE )))!=0){
+ rc = -EEFI ( efirc );
+ DBGC ( device, "SNP %p %s cannot open SNP protocol: %s\n",
+ device, 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;
+
+ /* Bring to the Started state */
+ if ( ( mode->State == EfiSimpleNetworkStopped ) &&
+ ( ( efirc = snp->snp->Start ( snp->snp ) ) != 0 ) ) {
+ rc = -EEFI ( efirc );
+ DBGC ( device, "SNP %p %s could not start: %s\n", device,
+ efi_handle_name ( device ), strerror ( rc ) );
+ goto err_start;
+ }
+ if ( ( mode->State == EfiSimpleNetworkInitialized ) &&
+ ( ( efirc = snp->snp->Shutdown ( snp->snp ) ) != 0 ) ) {
+ rc = -EEFI ( efirc );
+ DBGC ( device, "SNP %p %s could not shut down: %s\n", device,
+ 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 %p %s has invalid hardware address "
+ "length %d\n", device, 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 %p %s has invalid link-layer address "
+ "length %d\n", device, 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 %p %s registered as %s\n",
+ device, 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:
+ bs->CloseProtocol ( device, &efi_simple_network_protocol_guid,
+ efi_image_handle, device );
+ err_open_protocol:
+ return rc;
+}
+
+/**
+ * Detach driver from device
+ *
+ * @v efidev EFI device
+ */
+void snpnet_stop ( struct efi_device *efidev ) {
+ EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+ 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 */
+ if ( ( efirc = snp->snp->Stop ( snp->snp ) ) != 0 ) {
+ rc = -EEFI ( efirc );
+ DBGC ( device, "SNP %p %s could not stop: %s\n", device,
+ 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 */
+ bs->CloseProtocol ( device, &efi_simple_network_protocol_guid,
+ efi_image_handle, device );
+}