summaryrefslogtreecommitdiffstats
path: root/src/drivers/net/efi/mnpnet.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/drivers/net/efi/mnpnet.c')
-rw-r--r--src/drivers/net/efi/mnpnet.c563
1 files changed, 563 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..eb4b129c
--- /dev/null
+++ b/src/drivers/net/efi/mnpnet.c
@@ -0,0 +1,563 @@
+/*
+ * 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/cachedhcp.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/mnpnet.h>
+#include <ipxe/efi/Protocol/ManagedNetwork.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 );
+}
+
+/**
+ * Create temporary MNP network device
+ *
+ * @v handle MNP service binding handle
+ * @v netdev Network device to fill in
+ * @ret rc Return status code
+ */
+int mnptemp_create ( EFI_HANDLE handle, struct net_device **netdev ) {
+ struct efi_device *efidev;
+ int rc;
+
+ /* Create temporary EFI device */
+ efidev = efidev_alloc ( handle );
+ if ( ! efidev ) {
+ DBGC ( handle, "MNP %s could not create temporary device\n",
+ efi_handle_name ( handle ) );
+ rc = -ENOMEM;
+ goto err_alloc;
+ }
+
+ /* Start temporary network device */
+ if ( ( rc = mnpnet_start ( efidev ) ) != 0 ) {
+ DBGC ( handle, "MNP %s could not start MNP: %s\n",
+ efi_handle_name ( handle ), strerror ( rc ) );
+ goto err_start;
+ }
+
+ /* Fill in network device */
+ *netdev = efidev_get_drvdata ( efidev );
+
+ return 0;
+
+ mnpnet_stop ( efidev );
+ err_start:
+ efidev_free ( efidev );
+ err_alloc:
+ return rc;
+}
+
+/**
+ * Destroy temporary MNP network device
+ *
+ * @v netdev Network device
+ */
+void mnptemp_destroy ( struct net_device *netdev ) {
+ struct mnp_nic *mnp = netdev->priv;
+ struct efi_device *efidev = mnp->efidev;
+
+ /* Recycle any cached DHCP packet */
+ cachedhcp_recycle ( netdev );
+
+ /* Stop temporary network device */
+ mnpnet_stop ( efidev );
+
+ /* Free temporary EFI device */
+ efidev_free ( efidev );
+}