blob: eb4b129c7a1ec0ec5bed5b8a64c60d5a9728f392 [file] [log] [blame]
/*
* 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 );
}