From 89bb926a041b03c3926bf21266cbdf735d9aee66 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Wed, 13 Mar 2024 15:16:47 +0000 Subject: [efi] Provide a multiprocessor API for EFI Provide an implementation of the iPXE multiprocessor API for EFI, based on using EFI_MP_SERVICES to start up a wrapper function on all application processors. Note that the processor numbers used by EFI_MP_SERVICES are opaque integers that bear no relation to the underlying CPU identity (e.g. the APIC ID), and so we must rely on our own (architecture- specific) implementation to determine the relevant CPU identifiers. Signed-off-by: Michael Brown --- src/include/ipxe/errfile.h | 1 + 1 file changed, 1 insertion(+) (limited to 'src/include/ipxe/errfile.h') diff --git a/src/include/ipxe/errfile.h b/src/include/ipxe/errfile.h index 1fe34113..c673b9a4 100644 --- a/src/include/ipxe/errfile.h +++ b/src/include/ipxe/errfile.h @@ -81,6 +81,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define ERRFILE_efi_strings ( ERRFILE_CORE | 0x00290000 ) #define ERRFILE_uuid ( ERRFILE_CORE | 0x002a0000 ) #define ERRFILE_efi_path ( ERRFILE_CORE | 0x002b0000 ) +#define ERRFILE_efi_mp ( ERRFILE_CORE | 0x002c0000 ) #define ERRFILE_eisa ( ERRFILE_DRIVER | 0x00000000 ) #define ERRFILE_isa ( ERRFILE_DRIVER | 0x00010000 ) -- cgit v1.2.3-55-g7522 From ca483a196c091c16ea0a426ce5f915b184a34412 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Wed, 20 Mar 2024 12:47:25 +0000 Subject: [efi] Add helper functions for service binding protocols The EFI service binding abstraction is used to add and remove child handles for multiple different protocols. Provide a common interface for doing so. Signed-off-by: Michael Brown --- src/include/ipxe/efi/Protocol/ServiceBinding.h | 90 ++++++++++++++++ src/include/ipxe/efi/efi_service.h | 19 ++++ src/include/ipxe/errfile.h | 1 + src/interface/efi/efi_service.c | 138 +++++++++++++++++++++++++ 4 files changed, 248 insertions(+) create mode 100644 src/include/ipxe/efi/Protocol/ServiceBinding.h create mode 100644 src/include/ipxe/efi/efi_service.h create mode 100644 src/interface/efi/efi_service.c (limited to 'src/include/ipxe/errfile.h') diff --git a/src/include/ipxe/efi/Protocol/ServiceBinding.h b/src/include/ipxe/efi/Protocol/ServiceBinding.h new file mode 100644 index 00000000..6baf73aa --- /dev/null +++ b/src/include/ipxe/efi/Protocol/ServiceBinding.h @@ -0,0 +1,90 @@ +/** @file + UEFI Service Binding Protocol is defined in UEFI specification. + + The file defines the generic Service Binding Protocol functions. + It provides services that are required to create and destroy child + handles that support a given set of protocols. + + Copyright (c) 2006 - 2018, Intel Corporation. All rights reserved.
+ SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#ifndef __EFI_SERVICE_BINDING_H__ +#define __EFI_SERVICE_BINDING_H__ + +FILE_LICENCE ( BSD2_PATENT ); + +/// +/// Forward reference for pure ANSI compatability +/// +typedef struct _EFI_SERVICE_BINDING_PROTOCOL EFI_SERVICE_BINDING_PROTOCOL; + +/** + Creates a child handle and installs a protocol. + + The CreateChild() function installs a protocol on ChildHandle. + If ChildHandle is a pointer to NULL, then a new handle is created and returned in ChildHandle. + If ChildHandle is not a pointer to NULL, then the protocol installs on the existing ChildHandle. + + @param This Pointer to the EFI_SERVICE_BINDING_PROTOCOL instance. + @param ChildHandle Pointer to the handle of the child to create. If it is NULL, + then a new handle is created. If it is a pointer to an existing UEFI handle, + then the protocol is added to the existing UEFI handle. + + @retval EFI_SUCCES The protocol was added to ChildHandle. + @retval EFI_INVALID_PARAMETER ChildHandle is NULL. + @retval EFI_OUT_OF_RESOURCES There are not enough resources available to create + the child + @retval other The child handle was not created + +**/ +typedef +EFI_STATUS +(EFIAPI *EFI_SERVICE_BINDING_CREATE_CHILD)( + IN EFI_SERVICE_BINDING_PROTOCOL *This, + IN OUT EFI_HANDLE *ChildHandle + ); + +/** + Destroys a child handle with a protocol installed on it. + + The DestroyChild() function does the opposite of CreateChild(). It removes a protocol + that was installed by CreateChild() from ChildHandle. If the removed protocol is the + last protocol on ChildHandle, then ChildHandle is destroyed. + + @param This Pointer to the EFI_SERVICE_BINDING_PROTOCOL instance. + @param ChildHandle Handle of the child to destroy + + @retval EFI_SUCCES The protocol was removed from ChildHandle. + @retval EFI_UNSUPPORTED ChildHandle does not support the protocol that is being removed. + @retval EFI_INVALID_PARAMETER Child handle is NULL. + @retval EFI_ACCESS_DENIED The protocol could not be removed from the ChildHandle + because its services are being used. + @retval other The child handle was not destroyed + +**/ +typedef +EFI_STATUS +(EFIAPI *EFI_SERVICE_BINDING_DESTROY_CHILD)( + IN EFI_SERVICE_BINDING_PROTOCOL *This, + IN EFI_HANDLE ChildHandle + ); + +/// +/// The EFI_SERVICE_BINDING_PROTOCOL provides member functions to create and destroy +/// child handles. A driver is responsible for adding protocols to the child handle +/// in CreateChild() and removing protocols in DestroyChild(). It is also required +/// that the CreateChild() function opens the parent protocol BY_CHILD_CONTROLLER +/// to establish the parent-child relationship, and closes the protocol in DestroyChild(). +/// The pseudo code for CreateChild() and DestroyChild() is provided to specify the +/// required behavior, not to specify the required implementation. Each consumer of +/// a software protocol is responsible for calling CreateChild() when it requires the +/// protocol and calling DestroyChild() when it is finished with that protocol. +/// +struct _EFI_SERVICE_BINDING_PROTOCOL { + EFI_SERVICE_BINDING_CREATE_CHILD CreateChild; + EFI_SERVICE_BINDING_DESTROY_CHILD DestroyChild; +}; + +#endif diff --git a/src/include/ipxe/efi/efi_service.h b/src/include/ipxe/efi/efi_service.h new file mode 100644 index 00000000..ca4c7b2a --- /dev/null +++ b/src/include/ipxe/efi/efi_service.h @@ -0,0 +1,19 @@ +#ifndef _IPXE_EFI_SERVICE_H +#define _IPXE_EFI_SERVICE_H + +/** @file + * + * EFI service binding + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include + +extern int efi_service_add ( EFI_HANDLE service, EFI_GUID *binding, + EFI_HANDLE *handle ); +extern int efi_service_del ( EFI_HANDLE service, EFI_GUID *binding, + EFI_HANDLE handle ); + +#endif /* _IPXE_EFI_SERVICE_H */ diff --git a/src/include/ipxe/errfile.h b/src/include/ipxe/errfile.h index c673b9a4..083c77e1 100644 --- a/src/include/ipxe/errfile.h +++ b/src/include/ipxe/errfile.h @@ -82,6 +82,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define ERRFILE_uuid ( ERRFILE_CORE | 0x002a0000 ) #define ERRFILE_efi_path ( ERRFILE_CORE | 0x002b0000 ) #define ERRFILE_efi_mp ( ERRFILE_CORE | 0x002c0000 ) +#define ERRFILE_efi_service ( ERRFILE_CORE | 0x002d0000 ) #define ERRFILE_eisa ( ERRFILE_DRIVER | 0x00000000 ) #define ERRFILE_isa ( ERRFILE_DRIVER | 0x00010000 ) diff --git a/src/interface/efi/efi_service.c b/src/interface/efi/efi_service.c new file mode 100644 index 00000000..d4129c0d --- /dev/null +++ b/src/interface/efi/efi_service.c @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2024 Michael Brown . + * + * 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 + * + * EFI service binding + * + */ + +#include +#include +#include +#include +#include + +/** + * Add service to child handle + * + * @v service Service binding handle + * @v binding Service binding protocol GUID + * @v handle Handle on which to install child + * @ret rc Return status code + */ +int efi_service_add ( EFI_HANDLE service, EFI_GUID *binding, + EFI_HANDLE *handle ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + union { + EFI_SERVICE_BINDING_PROTOCOL *sb; + void *interface; + } u; + EFI_STATUS efirc; + int rc; + + /* Open service binding protocol */ + if ( ( efirc = bs->OpenProtocol ( service, binding, &u.interface, + efi_image_handle, service, + EFI_OPEN_PROTOCOL_GET_PROTOCOL ))!=0){ + rc = -EEFI ( efirc ); + DBGC ( service, "EFISVC %s cannot open %s binding: %s\n", + efi_handle_name ( service ), efi_guid_ntoa ( binding ), + strerror ( rc ) ); + goto err_open; + } + + /* Create child handle */ + if ( ( efirc = u.sb->CreateChild ( u.sb, handle ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( service, "EFISVC %s could not create %s child: %s\n", + efi_handle_name ( service ), efi_guid_ntoa ( binding ), + strerror ( rc ) ); + goto err_create; + } + + /* Success */ + rc = 0; + DBGC ( service, "EFISVC %s created %s child ", + efi_handle_name ( service ), efi_guid_ntoa ( binding ) ); + DBGC ( service, "%s\n", efi_handle_name ( *handle ) ); + + err_create: + bs->CloseProtocol ( service, binding, efi_image_handle, service ); + err_open: + return rc; +} + +/** + * Remove service from child handle + * + * @v service Service binding handle + * @v binding Service binding protocol GUID + * @v handle Child handle + * @ret rc Return status code + */ +int efi_service_del ( EFI_HANDLE service, EFI_GUID *binding, + EFI_HANDLE handle ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + union { + EFI_SERVICE_BINDING_PROTOCOL *sb; + void *interface; + } u; + EFI_STATUS efirc; + int rc; + + DBGC ( service, "EFISVC %s removing %s child ", + efi_handle_name ( service ), efi_guid_ntoa ( binding ) ); + DBGC ( service, "%s\n", efi_handle_name ( handle ) ); + + /* Open service binding protocol */ + if ( ( efirc = bs->OpenProtocol ( service, binding, &u.interface, + efi_image_handle, service, + EFI_OPEN_PROTOCOL_GET_PROTOCOL ))!=0){ + rc = -EEFI ( efirc ); + DBGC ( service, "EFISVC %s cannot open %s binding: %s\n", + efi_handle_name ( service ), efi_guid_ntoa ( binding ), + strerror ( rc ) ); + goto err_open; + } + + /* Destroy child handle */ + if ( ( efirc = u.sb->DestroyChild ( u.sb, handle ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( service, "EFISVC %s could not destroy %s child ", + efi_handle_name ( service ), efi_guid_ntoa ( binding ) ); + DBGC ( service, "%s: %s\n", + efi_handle_name ( handle ), strerror ( rc ) ); + goto err_destroy; + } + + /* Success */ + rc = 0; + + err_destroy: + bs->CloseProtocol ( service, binding, efi_image_handle, service ); + err_open: + return rc; +} -- cgit v1.2.3-55-g7522 From dcad73ca5ad3e1fe011c52a24036f67ad69fadc1 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Fri, 22 Mar 2024 15:30:45 +0000 Subject: [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 --- src/Makefile.efi | 4 +- src/drivers/net/efi/mnp.c | 56 +++++ src/drivers/net/efi/mnpnet.c | 504 ++++++++++++++++++++++++++++++++++++++++++ src/drivers/net/efi/mnpnet.h | 17 ++ src/drivers/net/efi/snp.c | 54 +---- src/drivers/net/efi/snpnet.c | 48 ++++ src/drivers/net/efi/snpnet.h | 1 + src/drivers/net/efi/snponly.c | 26 +++ src/include/ipxe/errfile.h | 1 + 9 files changed, 657 insertions(+), 54 deletions(-) create mode 100644 src/drivers/net/efi/mnp.c create mode 100644 src/drivers/net/efi/mnpnet.c create mode 100644 src/drivers/net/efi/mnpnet.h (limited to 'src/include/ipxe/errfile.h') diff --git a/src/Makefile.efi b/src/Makefile.efi index 6e8ad46b..95ecf386 100644 --- a/src/Makefile.efi +++ b/src/Makefile.efi @@ -23,9 +23,9 @@ NON_AUTO_MEDIA += efidrv NON_AUTO_MEDIA += drv.efi NON_AUTO_MEDIA += efirom -# Include SNP driver in the all-drivers build +# Include SNP and MNP drivers in the all-drivers build # -DRIVERS_net += snp +DRIVERS_net += snp mnp # Rules for building EFI files # diff --git a/src/drivers/net/efi/mnp.c b/src/drivers/net/efi/mnp.c new file mode 100644 index 00000000..b79fe188 --- /dev/null +++ b/src/drivers/net/efi/mnp.c @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2024 Michael Brown . + * + * 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 driver + * + */ + +#include +#include +#include +#include "snpnet.h" +#include "mnpnet.h" + +/** + * Check to see if driver supports a device + * + * @v device EFI device handle + * @ret rc Return status code + */ +static int mnp_supported ( EFI_HANDLE device ) { + EFI_GUID *binding = &efi_managed_network_service_binding_protocol_guid; + + return snpnet_supported ( device, binding ); +} + +/** EFI MNP driver */ +struct efi_driver mnp_driver __efi_driver ( EFI_DRIVER_NORMAL ) = { + .name = "MNP", + .supported = mnp_supported, + .start = mnpnet_start, + .stop = mnpnet_stop, +}; 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 . + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 ); +} diff --git a/src/drivers/net/efi/mnpnet.h b/src/drivers/net/efi/mnpnet.h new file mode 100644 index 00000000..afed62aa --- /dev/null +++ b/src/drivers/net/efi/mnpnet.h @@ -0,0 +1,17 @@ +#ifndef _MNPNET_H +#define _MNPNET_H + +/** @file + * + * MNP NIC driver + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +struct efi_device; + +extern int mnpnet_start ( struct efi_device *efidev ); +extern void mnpnet_stop ( struct efi_device *efidev ); + +#endif /* _MNPNET_H */ diff --git a/src/drivers/net/efi/snp.c b/src/drivers/net/efi/snp.c index 1920cdbc..cac8b38e 100644 --- a/src/drivers/net/efi/snp.c +++ b/src/drivers/net/efi/snp.c @@ -23,11 +23,8 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); -#include #include #include -#include -#include #include "snpnet.h" #include "nii.h" @@ -37,53 +34,6 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); * */ -/** - * Check to see if driver supports a device - * - * @v device EFI device handle - * @v protocol Protocol GUID - * @ret rc Return status code - */ -static int snp_nii_supported ( EFI_HANDLE device, EFI_GUID *protocol ) { - EFI_BOOT_SERVICES *bs = efi_systab->BootServices; - EFI_HANDLE parent; - EFI_STATUS efirc; - int rc; - - /* Check that this is not a device we are providing ourselves */ - if ( find_snpdev ( device ) != NULL ) { - DBGCP ( device, "HANDLE %s is provided by this binary\n", - efi_handle_name ( device ) ); - return -ENOTTY; - } - - /* Test for presence of protocol */ - if ( ( efirc = bs->OpenProtocol ( device, protocol, - NULL, efi_image_handle, device, - EFI_OPEN_PROTOCOL_TEST_PROTOCOL))!=0){ - DBGCP ( device, "HANDLE %s is not a %s device\n", - efi_handle_name ( device ), - efi_guid_ntoa ( protocol ) ); - return -EEFI ( efirc ); - } - - /* Check that there are no instances of this protocol further - * up this device path. - */ - if ( ( rc = efi_locate_device ( device, protocol, - &parent, 1 ) ) == 0 ) { - DBGC2 ( device, "HANDLE %s has %s-supporting parent ", - efi_handle_name ( device ), - efi_guid_ntoa ( protocol ) ); - DBGC2 ( device, "%s\n", efi_handle_name ( parent ) ); - return -ENOTTY; - } - - DBGC ( device, "HANDLE %s is a %s device\n", - efi_handle_name ( device ), efi_guid_ntoa ( protocol ) ); - return 0; -} - /** * Check to see if driver supports a device * @@ -92,7 +42,7 @@ static int snp_nii_supported ( EFI_HANDLE device, EFI_GUID *protocol ) { */ static int snp_supported ( EFI_HANDLE device ) { - return snp_nii_supported ( device, &efi_simple_network_protocol_guid ); + return snpnet_supported ( device, &efi_simple_network_protocol_guid ); } /** @@ -103,7 +53,7 @@ static int snp_supported ( EFI_HANDLE device ) { */ static int nii_supported ( EFI_HANDLE device ) { - return snp_nii_supported ( device, &efi_nii31_protocol_guid ); + return snpnet_supported ( device, &efi_nii31_protocol_guid ); } /** EFI SNP driver */ diff --git a/src/drivers/net/efi/snpnet.c b/src/drivers/net/efi/snpnet.c index b8bf963e..6ce731d7 100644 --- a/src/drivers/net/efi/snpnet.c +++ b/src/drivers/net/efi/snpnet.c @@ -33,6 +33,7 @@ FILE_LICENCE ( GPL2_OR_LATER ); #include #include #include +#include #include "snpnet.h" /** @file @@ -484,6 +485,53 @@ static struct net_device_operations snpnet_operations = { .poll = snpnet_poll, }; +/** + * Check to see if driver supports a device + * + * @v device EFI device handle + * @v protocol Protocol GUID + * @ret rc Return status code + */ +int snpnet_supported ( EFI_HANDLE device, EFI_GUID *protocol ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + EFI_HANDLE parent; + EFI_STATUS efirc; + int rc; + + /* Check that this is not a device we are providing ourselves */ + if ( find_snpdev ( device ) != NULL ) { + DBGCP ( device, "HANDLE %s is provided by this binary\n", + efi_handle_name ( device ) ); + return -ENOTTY; + } + + /* Test for presence of protocol */ + if ( ( efirc = bs->OpenProtocol ( device, protocol, + NULL, efi_image_handle, device, + EFI_OPEN_PROTOCOL_TEST_PROTOCOL))!=0){ + DBGCP ( device, "HANDLE %s is not a %s device\n", + efi_handle_name ( device ), + efi_guid_ntoa ( protocol ) ); + return -EEFI ( efirc ); + } + + /* Check that there are no instances of this protocol further + * up this device path. + */ + if ( ( rc = efi_locate_device ( device, protocol, + &parent, 1 ) ) == 0 ) { + DBGC2 ( device, "HANDLE %s has %s-supporting parent ", + efi_handle_name ( device ), + efi_guid_ntoa ( protocol ) ); + DBGC2 ( device, "%s\n", efi_handle_name ( parent ) ); + return -ENOTTY; + } + + DBGC ( device, "HANDLE %s is a %s device\n", + efi_handle_name ( device ), efi_guid_ntoa ( protocol ) ); + return 0; +} + /** * Attach driver to device * diff --git a/src/drivers/net/efi/snpnet.h b/src/drivers/net/efi/snpnet.h index e6d31d5e..4699c789 100644 --- a/src/drivers/net/efi/snpnet.h +++ b/src/drivers/net/efi/snpnet.h @@ -11,6 +11,7 @@ FILE_LICENCE ( GPL2_OR_LATER ); struct efi_device; +extern int snpnet_supported ( EFI_HANDLE device, EFI_GUID *protocol ); extern int snpnet_start ( struct efi_device *efidev ); extern void snpnet_stop ( struct efi_device *efidev ); diff --git a/src/drivers/net/efi/snponly.c b/src/drivers/net/efi/snponly.c index 16370463..6786f3e8 100644 --- a/src/drivers/net/efi/snponly.c +++ b/src/drivers/net/efi/snponly.c @@ -32,6 +32,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include #include #include "snpnet.h" +#include "mnpnet.h" #include "nii.h" /** @file @@ -74,6 +75,11 @@ static struct chained_protocol chained_nii = { .protocol = &efi_nii31_protocol_guid, }; +/** Chainloaded MNP protocol */ +static struct chained_protocol chained_mnp = { + .protocol = &efi_managed_network_service_binding_protocol_guid, +}; + /** * Locate chainloaded protocol * @@ -208,6 +214,17 @@ static int niionly_supported ( EFI_HANDLE device ) { return chained_supported ( device, &chained_nii ); } +/** + * Check to see if driver supports a device + * + * @v device EFI device handle + * @ret rc Return status code + */ +static int mnponly_supported ( EFI_HANDLE device ) { + + return chained_supported ( device, &chained_mnp ); +} + /** EFI SNP chainloading-device-only driver */ struct efi_driver snponly_driver __efi_driver ( EFI_DRIVER_NORMAL ) = { .name = "SNPONLY", @@ -224,6 +241,14 @@ struct efi_driver niionly_driver __efi_driver ( EFI_DRIVER_NORMAL ) = { .stop = nii_stop, }; +/** EFI MNP chainloading-device-only driver */ +struct efi_driver mnponly_driver __efi_driver ( EFI_DRIVER_NORMAL ) = { + .name = "MNPONLY", + .supported = mnponly_supported, + .start = mnpnet_start, + .stop = mnpnet_stop, +}; + /** * Initialise EFI chainloaded-device-only driver * @@ -232,6 +257,7 @@ static void chained_init ( void ) { chained_locate ( &chained_snp ); chained_locate ( &chained_nii ); + chained_locate ( &chained_mnp ); } /** EFI chainloaded-device-only initialisation function */ diff --git a/src/include/ipxe/errfile.h b/src/include/ipxe/errfile.h index 083c77e1..0ab37230 100644 --- a/src/include/ipxe/errfile.h +++ b/src/include/ipxe/errfile.h @@ -223,6 +223,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define ERRFILE_ice ( ERRFILE_DRIVER | 0x00d20000 ) #define ERRFILE_ecam ( ERRFILE_DRIVER | 0x00d30000 ) #define ERRFILE_pcibridge ( ERRFILE_DRIVER | 0x00d40000 ) +#define ERRFILE_mnpnet ( ERRFILE_DRIVER | 0x00d50000 ) #define ERRFILE_aoe ( ERRFILE_NET | 0x00000000 ) #define ERRFILE_arp ( ERRFILE_NET | 0x00010000 ) -- cgit v1.2.3-55-g7522