summaryrefslogtreecommitdiffstats
path: root/src/drivers/net/efi/mnpnet.c
diff options
context:
space:
mode:
authorMichael Brown2024-03-22 16:30:45 +0100
committerMichael Brown2024-03-25 18:58:33 +0100
commitdcad73ca5ad3e1fe011c52a24036f67ad69fadc1 (patch)
treeecb05e55a634d6d63cc46f6f3a02eb3930e30eb6 /src/drivers/net/efi/mnpnet.c
parent[efi] Allow for drivers to be located via child handles (diff)
downloadipxe-dcad73ca5ad3e1fe011c52a24036f67ad69fadc1.tar.gz
ipxe-dcad73ca5ad3e1fe011c52a24036f67ad69fadc1.tar.xz
ipxe-dcad73ca5ad3e1fe011c52a24036f67ad69fadc1.zip
[efi] Add support for driving EFI_MANAGED_NETWORK_PROTOCOL devices
We want exclusive access to the network device, both for performance reasons and because we perform operations such as EAPoL that affect the entire link. We currently drive the network card via either a native hardware driver or via the SNP or NII/UNDI interfaces, both of which grant us this exclusive access. Add an alternative driver that drives the network card non-exclusively via the EFI_MANAGED_NETWORK_PROTOCOL interface. This can function as a fallback for situations where neither SNP nor NII/UNDI interfaces are functional, and also opens up the possibility of non-destructively installing a temporary network device over which to download the autoexec.ipxe script. Signed-off-by: Michael Brown <mcb30@ipxe.org>
Diffstat (limited to 'src/drivers/net/efi/mnpnet.c')
-rw-r--r--src/drivers/net/efi/mnpnet.c504
1 files changed, 504 insertions, 0 deletions
diff --git a/src/drivers/net/efi/mnpnet.c b/src/drivers/net/efi/mnpnet.c
new file mode 100644
index 00000000..a07eae54
--- /dev/null
+++ b/src/drivers/net/efi/mnpnet.c
@@ -0,0 +1,504 @@
+/*
+ * Copyright (C) 2024 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.
+ *
+ * You can also choose to distribute this program under the terms of
+ * the Unmodified Binary Distribution Licence (as given in the file
+ * COPYING.UBDL), provided that you have satisfied its requirements.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
+
+/** @file
+ *
+ * MNP NIC driver
+ *
+ */
+
+#include <string.h>
+#include <errno.h>
+#include <ipxe/iobuf.h>
+#include <ipxe/netdevice.h>
+#include <ipxe/ethernet.h>
+#include <ipxe/efi/efi.h>
+#include <ipxe/efi/efi_driver.h>
+#include <ipxe/efi/efi_service.h>
+#include <ipxe/efi/efi_utils.h>
+#include <ipxe/efi/Protocol/ManagedNetwork.h>
+#include "mnpnet.h"
+
+/** An MNP transmit or receive token */
+struct mnp_token {
+ /** MNP completion token */
+ EFI_MANAGED_NETWORK_COMPLETION_TOKEN token;
+ /** Token is owned by MNP */
+ int busy;
+};
+
+/** An MNP NIC */
+struct mnp_nic {
+ /** EFI device */
+ struct efi_device *efidev;
+ /** Managed network protocol */
+ EFI_MANAGED_NETWORK_PROTOCOL *mnp;
+ /** Generic device */
+ struct device dev;
+
+ /** Transmit token */
+ struct mnp_token tx;
+ /** Transmit descriptor */
+ EFI_MANAGED_NETWORK_TRANSMIT_DATA txdata;
+ /** Transmit I/O buffer */
+ struct io_buffer *txbuf;
+
+ /** Receive token */
+ struct mnp_token rx;
+};
+
+/**
+ * Transmit or receive token event
+ *
+ * @v event Event
+ * @v context Event context
+ */
+static VOID EFIAPI mnpnet_event ( EFI_EVENT event __unused, VOID *context ) {
+ struct mnp_token *token = context;
+
+ /* Sanity check */
+ assert ( token->busy );
+
+ /* Mark token as no longer owned by MNP */
+ token->busy = 0;
+}
+
+/**
+ * Transmit packet
+ *
+ * @v netdev Network device
+ * @v iobuf I/O buffer
+ * @ret rc Return status code
+ */
+static int mnpnet_transmit ( struct net_device *netdev,
+ struct io_buffer *iobuf ) {
+ struct mnp_nic *mnp = netdev->priv;
+ struct ll_protocol *ll_protocol = netdev->ll_protocol;
+ 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 ( mnp->txbuf ) {
+ netdev_tx_defer ( netdev, iobuf );
+ return 0;
+ }
+
+ /* Construct transmit token */
+ mnp->txdata.DataLength =
+ ( iob_len ( iobuf ) - ll_protocol->ll_header_len );
+ mnp->txdata.HeaderLength = ll_protocol->ll_header_len;
+ mnp->txdata.FragmentCount = 1;
+ mnp->txdata.FragmentTable[0].FragmentLength = iob_len ( iobuf );
+ mnp->txdata.FragmentTable[0].FragmentBuffer = iobuf->data;
+ mnp->tx.token.Packet.TxData = &mnp->txdata;
+
+ /* Record as in use */
+ mnp->tx.busy = 1;
+
+ /* Transmit packet */
+ if ( ( efirc = mnp->mnp->Transmit ( mnp->mnp, &mnp->tx.token ) ) != 0 ){
+ rc = -EEFI ( efirc );
+ DBGC ( mnp, "MNP %s could not transmit: %s\n",
+ netdev->name, strerror ( rc ) );
+ mnp->tx.busy = 0;
+ return rc;
+ }
+
+ /* Record I/O buffer */
+ mnp->txbuf = iobuf;
+
+ return 0;
+}
+
+/**
+ * Refill receive token
+ *
+ * @v netdev Network device
+ */
+static void mnpnet_refill_rx ( struct net_device *netdev ) {
+ struct mnp_nic *mnp = netdev->priv;
+ EFI_STATUS efirc;
+ int rc;
+
+ /* Do nothing if receive token is still in use */
+ if ( mnp->rx.busy )
+ return;
+
+ /* Mark as in use */
+ mnp->rx.busy = 1;
+
+ /* Queue receive token */
+ if ( ( efirc = mnp->mnp->Receive ( mnp->mnp, &mnp->rx.token ) ) != 0 ) {
+ rc = -EEFI ( efirc );
+ DBGC ( mnp, "MNP %s could not receive: %s\n",
+ netdev->name, strerror ( rc ) );
+ /* Wait for next refill */
+ mnp->rx.busy = 0;
+ return;
+ }
+}
+
+/**
+ * Poll for completed packets
+ *
+ * @v netdev Network device
+ */
+static void mnpnet_poll_tx ( struct net_device *netdev ) {
+ struct mnp_nic *mnp = netdev->priv;
+ struct io_buffer *iobuf;
+ EFI_STATUS efirc;
+ int rc;
+
+ /* Do nothing if transmit token is still in use */
+ if ( mnp->tx.busy )
+ return;
+
+ /* Do nothing unless we have a completion */
+ if ( ! mnp->txbuf )
+ return;
+
+ /* Get completion status */
+ efirc = mnp->tx.token.Status;
+ rc = ( efirc ? -EEFI ( efirc ) : 0 );
+
+ /* Complete transmission */
+ iobuf = mnp->txbuf;
+ mnp->txbuf = NULL;
+ netdev_tx_complete_err ( netdev, iobuf, rc );
+}
+
+/**
+ * Poll for received packets
+ *
+ * @v netdev Network device
+ */
+static void mnpnet_poll_rx ( struct net_device *netdev ) {
+ EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+ struct mnp_nic *mnp = netdev->priv;
+ EFI_MANAGED_NETWORK_RECEIVE_DATA *rxdata;
+ struct io_buffer *iobuf;
+ size_t len;
+ EFI_STATUS efirc;
+ int rc;
+
+ /* Do nothing unless we have a completion */
+ if ( mnp->rx.busy )
+ return;
+ rxdata = mnp->rx.token.Packet.RxData;
+
+ /* Get completion status */
+ if ( ( efirc = mnp->rx.token.Status ) != 0 ) {
+ rc = -EEFI ( efirc );
+ netdev_rx_err ( netdev, NULL, rc );
+ goto recycle;
+ }
+
+ /* Allocate and fill I/O buffer */
+ len = rxdata->PacketLength;
+ iobuf = alloc_iob ( len );
+ if ( ! iobuf ) {
+ netdev_rx_err ( netdev, NULL, -ENOMEM );
+ goto recycle;
+ }
+ memcpy ( iob_put ( iobuf, len ), rxdata->MediaHeader, len );
+
+ /* Hand off to network stack */
+ netdev_rx ( netdev, iobuf );
+
+ recycle:
+ /* Recycle token */
+ bs->SignalEvent ( rxdata->RecycleEvent );
+}
+
+/**
+ * Poll for completed packets
+ *
+ * @v netdev Network device
+ */
+static void mnpnet_poll ( struct net_device *netdev ) {
+ struct mnp_nic *mnp = netdev->priv;
+
+ /* Do nothing if shutdown is in progress */
+ if ( efi_shutdown_in_progress )
+ return;
+
+ /* Poll interface */
+ mnp->mnp->Poll ( mnp->mnp );
+
+ /* Process any transmit completions */
+ mnpnet_poll_tx ( netdev );
+
+ /* Process any receive completions */
+ mnpnet_poll_rx ( netdev );
+
+ /* Refill receive token */
+ mnpnet_refill_rx ( netdev );
+}
+
+/**
+ * Open network device
+ *
+ * @v netdev Network device
+ * @ret rc Return status code
+ */
+static int mnpnet_open ( struct net_device *netdev ) {
+ EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+ static EFI_MANAGED_NETWORK_CONFIG_DATA config = {
+ .EnableUnicastReceive = TRUE,
+ .EnableMulticastReceive = TRUE,
+ .EnableBroadcastReceive = TRUE,
+ .EnablePromiscuousReceive = TRUE,
+ .FlushQueuesOnReset = TRUE,
+ .DisableBackgroundPolling = TRUE,
+ };
+ struct mnp_nic *mnp = netdev->priv;
+ EFI_STATUS efirc;
+ int rc;
+
+ /* Create transmit event */
+ if ( ( efirc = bs->CreateEvent ( EVT_NOTIFY_SIGNAL, TPL_NOTIFY,
+ mnpnet_event, &mnp->tx,
+ &mnp->tx.token.Event ) ) != 0 ) {
+ rc = -EEFI ( efirc );
+ DBGC ( mnp, "MNP %s could not create TX event: %s\n",
+ netdev->name, strerror ( rc ) );
+ goto err_tx_event;
+ }
+
+ /* Create receive event */
+ if ( ( efirc = bs->CreateEvent ( EVT_NOTIFY_SIGNAL, TPL_NOTIFY,
+ mnpnet_event, &mnp->rx,
+ &mnp->rx.token.Event ) ) != 0 ) {
+ rc = -EEFI ( efirc );
+ DBGC ( mnp, "MNP %s could not create RX event: %s\n",
+ netdev->name, strerror ( rc ) );
+ goto err_rx_event;
+ }
+
+ /* Configure MNP */
+ if ( ( efirc = mnp->mnp->Configure ( mnp->mnp, &config ) ) != 0 ) {
+ rc = -EEFI ( efirc );
+ DBGC ( mnp, "MNP %s could not configure: %s\n",
+ netdev->name, strerror ( rc ) );
+ goto err_configure;
+ }
+
+ /* Refill receive token */
+ mnpnet_refill_rx ( netdev );
+
+ return 0;
+
+ mnp->mnp->Configure ( mnp->mnp, NULL );
+ err_configure:
+ bs->CloseEvent ( mnp->rx.token.Event );
+ err_rx_event:
+ bs->CloseEvent ( mnp->tx.token.Event );
+ err_tx_event:
+ return rc;
+}
+
+/**
+ * Close network device
+ *
+ * @v netdev Network device
+ */
+static void mnpnet_close ( struct net_device *netdev ) {
+ EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+ struct mnp_nic *mnp = netdev->priv;
+
+ /* Reset MNP (unless whole system shutdown is in progress) */
+ if ( ! efi_shutdown_in_progress )
+ mnp->mnp->Configure ( mnp->mnp, NULL );
+
+ /* Close events */
+ bs->CloseEvent ( mnp->rx.token.Event );
+ bs->CloseEvent ( mnp->tx.token.Event );
+
+ /* Reset tokens */
+ mnp->tx.busy = 0;
+ mnp->rx.busy = 0;
+
+ /* Discard any incomplete I/O buffer */
+ if ( mnp->txbuf ) {
+ netdev_tx_complete_err ( netdev, mnp->txbuf, -ECANCELED );
+ mnp->txbuf = NULL;
+ }
+}
+
+/** MNP network device operations */
+static struct net_device_operations mnpnet_operations = {
+ .open = mnpnet_open,
+ .close = mnpnet_close,
+ .transmit = mnpnet_transmit,
+ .poll = mnpnet_poll,
+};
+
+/**
+ * Attach driver to device
+ *
+ * @v efidev EFI device
+ * @ret rc Return status code
+ */
+int mnpnet_start ( struct efi_device *efidev ) {
+ EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+ EFI_HANDLE device = efidev->device;
+ EFI_GUID *binding = &efi_managed_network_service_binding_protocol_guid;
+ EFI_SIMPLE_NETWORK_MODE mode;
+ union {
+ EFI_MANAGED_NETWORK_PROTOCOL *mnp;
+ void *interface;
+ } u;
+ struct net_device *netdev;
+ struct mnp_nic *mnp;
+ EFI_STATUS efirc;
+ int rc;
+
+ /* Allocate and initalise structure */
+ netdev = alloc_etherdev ( sizeof ( *mnp ) );
+ if ( ! netdev ) {
+ rc = -ENOMEM;
+ goto err_alloc;
+ }
+ netdev_init ( netdev, &mnpnet_operations );
+ mnp = netdev->priv;
+ mnp->efidev = efidev;
+ efidev_set_drvdata ( efidev, netdev );
+
+ /* Populate underlying device information */
+ efi_device_info ( device, "MNP", &mnp->dev );
+ mnp->dev.driver_name = "MNP";
+ mnp->dev.parent = &efidev->dev;
+ list_add ( &mnp->dev.siblings, &efidev->dev.children );
+ INIT_LIST_HEAD ( &mnp->dev.children );
+ netdev->dev = &mnp->dev;
+
+ /* Create MNP child */
+ if ( ( rc = efi_service_add ( device, binding,
+ &efidev->child ) ) != 0 ) {
+ DBGC ( mnp, "MNP %s could not create child: %s\n",
+ efi_handle_name ( device ), strerror ( rc ) );
+ goto err_service;
+ }
+
+ /* Open MNP protocol */
+ if ( ( efirc = bs->OpenProtocol ( efidev->child,
+ &efi_managed_network_protocol_guid,
+ &u.interface, efi_image_handle,
+ efidev->child,
+ ( EFI_OPEN_PROTOCOL_BY_DRIVER |
+ EFI_OPEN_PROTOCOL_EXCLUSIVE )))!=0){
+ rc = -EEFI ( efirc );
+ DBGC ( mnp, "MNP %s could not open MNP protocol: %s\n",
+ efi_handle_name ( device ), strerror ( rc ) );
+ goto err_open;
+ }
+ mnp->mnp = u.mnp;
+
+ /* Get configuration */
+ efirc = mnp->mnp->GetModeData ( mnp->mnp, NULL, &mode );
+ if ( ( efirc != 0 ) && ( efirc != EFI_NOT_STARTED ) ) {
+ rc = -EEFI ( efirc );
+ DBGC ( mnp, "MNP %s could not get mode data: %s\n",
+ efi_handle_name ( device ), strerror ( rc ) );
+ goto err_mode;
+ }
+
+ /* Populate network device parameters */
+ if ( mode.HwAddressSize != netdev->ll_protocol->hw_addr_len ) {
+ DBGC ( device, "MNP %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, "MNP %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 );
+
+ /* Register network device */
+ if ( ( rc = register_netdev ( netdev ) ) != 0 )
+ goto err_register;
+ DBGC ( mnp, "MNP %s registered as %s\n",
+ efi_handle_name ( device ), netdev->name );
+
+ /* Mark as link up: we don't handle link state */
+ netdev_link_up ( netdev );
+
+ return 0;
+
+ unregister_netdev ( netdev );
+ err_register:
+ err_ll_addr_len:
+ err_hw_addr_len:
+ err_mode:
+ bs->CloseProtocol ( efidev->child, &efi_managed_network_protocol_guid,
+ efi_image_handle, efidev->child );
+ err_open:
+ efi_service_del ( device, binding, efidev->child );
+ err_service:
+ list_del ( &mnp->dev.siblings );
+ netdev_nullify ( netdev );
+ netdev_put ( netdev );
+ err_alloc:
+ return rc;
+}
+
+/**
+ * Detach driver from device
+ *
+ * @v efidev EFI device
+ */
+void mnpnet_stop ( struct efi_device *efidev ) {
+ EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+ EFI_GUID *binding = &efi_managed_network_service_binding_protocol_guid;
+ struct net_device *netdev = efidev_get_drvdata ( efidev );
+ struct mnp_nic *mnp = netdev->priv;
+
+ /* Unregister network device */
+ unregister_netdev ( netdev );
+
+ /* Close MNP protocol */
+ bs->CloseProtocol ( efidev->child, &efi_managed_network_protocol_guid,
+ efi_image_handle, efidev->child );
+
+ /* Remove MNP child (unless whole system shutdown is in progress) */
+ if ( ! efi_shutdown_in_progress )
+ efi_service_del ( efidev->device, binding, efidev->child );
+
+ /* Free network device */
+ list_del ( &mnp->dev.siblings );
+ netdev_nullify ( netdev );
+ netdev_put ( netdev );
+}