diff options
Diffstat (limited to 'src/drivers/net/efi/mnpnet.c')
-rw-r--r-- | src/drivers/net/efi/mnpnet.c | 504 |
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 000000000..a07eae54e --- /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 ); +} |