--- /dev/null
+/*
+ * Copyright (C) 2025 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
+ *
+ * EFI protocol opening and closing
+ *
+ * The UEFI model for opening and closing protocols is broken by
+ * design and cannot be repaired.
+ *
+ * Calling OpenProtocol() to obtain a protocol interface pointer does
+ * not, in general, provide any guarantees about the lifetime of that
+ * pointer. It is theoretically possible that the pointer has already
+ * become invalid by the time that OpenProtocol() returns the pointer
+ * to its caller. (This can happen when a USB device is physically
+ * removed, for example.)
+ *
+ * Various UEFI design flaws make it occasionally necessary to hold on
+ * to a protocol interface pointer despite the total lack of
+ * guarantees that the pointer will remain valid.
+ *
+ * The UEFI driver model overloads the semantics of OpenProtocol() to
+ * accommodate the use cases of recording a driver attachment (which
+ * is modelled as opening a protocol with EFI_OPEN_PROTOCOL_BY_DRIVER
+ * attributes) and recording the existence of a related child
+ * controller (which is modelled as opening a protocol with
+ * EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER attributes).
+ *
+ * The parameters defined for CloseProtocol() are not sufficient to
+ * allow the implementation to precisely identify the matching call to
+ * OpenProtocol(). While the UEFI model appears to allow for matched
+ * open and close pairs, this is merely an illusion. Calling
+ * CloseProtocol() will delete *all* matching records in the protocol
+ * open information tables.
+ *
+ * Since the parameters defined for CloseProtocol() do not include the
+ * attributes passed to OpenProtocol(), this means that a matched
+ * open/close pair using EFI_OPEN_PROTOCOL_GET_PROTOCOL can
+ * inadvertently end up deleting the record that defines a driver
+ * attachment or the existence of a child controller. This in turn
+ * can cause some very unexpected side effects, such as allowing other
+ * UEFI drivers to start controlling hardware to which iPXE believes
+ * it has exclusive access. This rarely ends well.
+ *
+ * To prevent this kind of inadvertent deletion, we establish a
+ * convention for four different types of protocol opening:
+ *
+ * - ephemeral opens: always opened with ControllerHandle = NULL
+ *
+ * - unsafe opens: always opened with ControllerHandle = AgentHandle
+ *
+ * - by-driver opens: always opened with ControllerHandle = Handle
+ *
+ * - by-child opens: always opened with ControllerHandle != Handle
+ *
+ * This convention ensures that the four types of open never overlap
+ * within the set of parameters defined for CloseProtocol(), and so a
+ * close of one type cannot inadvertently delete the record
+ * corresponding to a different type.
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <ipxe/efi/efi.h>
+
+/**
+ * Open (or test) protocol for ephemeral use
+ *
+ * @v handle EFI handle
+ * @v protocol Protocol GUID
+ * @v interface Protocol interface pointer to fill in (or NULL to test)
+ * @ret rc Return status code
+ */
+int efi_open ( EFI_HANDLE handle, EFI_GUID *protocol, void **interface ) {
+ EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+ EFI_HANDLE agent = efi_image_handle;
+ EFI_HANDLE controller;
+ unsigned int attributes;
+ EFI_STATUS efirc;
+ int rc;
+
+ /* Sanity checks */
+ assert ( handle != NULL );
+ assert ( protocol != NULL );
+
+ /* Open protocol
+ *
+ * We set ControllerHandle to NULL to avoid collisions with
+ * other open types.
+ */
+ controller = NULL;
+ attributes = ( interface ? EFI_OPEN_PROTOCOL_GET_PROTOCOL :
+ EFI_OPEN_PROTOCOL_TEST_PROTOCOL );
+ if ( ( efirc = bs->OpenProtocol ( handle, protocol, interface, agent,
+ controller, attributes ) ) != 0 ) {
+ rc = -EEFI ( efirc );
+ if ( interface )
+ *interface = NULL;
+ return rc;
+ }
+
+ /* Close protocol immediately
+ *
+ * While it may seem prima facie unsafe to use a protocol
+ * after closing it, UEFI doesn't actually give us any safety
+ * even while the protocol is nominally open. Opening a
+ * protocol with EFI_OPEN_PROTOCOL_GET_PROTOCOL attributes
+ * does not in any way ensure that the interface pointer
+ * remains valid: there are no locks or notifications
+ * associated with these "opens".
+ *
+ * The only way to obtain a (partially) guaranteed persistent
+ * interface pointer is to open the protocol with the
+ * EFI_OPEN_PROTOCOL_BY_DRIVER attributes. This is not
+ * possible in the general case, since UEFI permits only a
+ * single image at a time to have the protocol opened with
+ * these attributes.
+ *
+ * We can therefore obtain at best an ephemeral interface
+ * pointer: one that is guaranteed to remain valid only for as
+ * long as we do not relinquish the thread of control.
+ *
+ * (Since UEFI permits calls to UninstallProtocolInterface()
+ * at levels up to and including TPL_NOTIFY, this means that
+ * we technically cannot rely on the pointer remaining valid
+ * unless the caller is itself running at TPL_NOTIFY. This is
+ * clearly impractical, and large portions of the EDK2
+ * codebase presume that using EFI_OPEN_PROTOCOL_GET_PROTOCOL
+ * is safe at lower TPLs.)
+ *
+ * Closing is not strictly necessary for protocols opened
+ * ephemerally (i.e. using EFI_OPEN_PROTOCOL_GET_PROTOCOL or
+ * EFI_OPEN_PROTOCOL_TEST_PROTOCOL), but avoids polluting the
+ * protocol open information tables with stale data.
+ *
+ * Closing immediately also simplifies the callers' code
+ * paths, since they do not need to worry about closing the
+ * protocol.
+ *
+ * The overall effect is equivalent to using HandleProtocol(),
+ * but without the associated pollution of the protocol open
+ * information tables, and with improved traceability.
+ */
+ bs->CloseProtocol ( handle, protocol, agent, controller );
+
+ return 0;
+}
+
+/**
+ * Open protocol for unsafe persistent use
+ *
+ * @v handle EFI handle
+ * @v protocol Protocol GUID
+ * @v interface Protocol interface pointer to fill in
+ * @ret rc Return status code
+ */
+int efi_open_unsafe ( EFI_HANDLE handle, EFI_GUID *protocol,
+ void **interface ) {
+ EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+ EFI_HANDLE agent = efi_image_handle;
+ EFI_HANDLE controller;
+ unsigned int attributes;
+ EFI_STATUS efirc;
+ int rc;
+
+ /* Sanity checks */
+ assert ( handle != NULL );
+ assert ( protocol != NULL );
+ assert ( interface != NULL );
+
+ /* Open protocol
+ *
+ * We set ControllerHandle equal to AgentHandle to avoid
+ * collisions with other open types.
+ */
+ controller = agent;
+ attributes = EFI_OPEN_PROTOCOL_GET_PROTOCOL;
+ if ( ( efirc = bs->OpenProtocol ( handle, protocol, interface, agent,
+ controller, attributes ) ) != 0 ) {
+ rc = -EEFI ( efirc );
+ *interface = NULL;
+ return rc;
+ }
+
+ return 0;
+}
+
+/**
+ * Close protocol opened for unsafe persistent use
+ *
+ * @v handle EFI handle
+ * @v protocol Protocol GUID
+ * @v child Child controller handle
+ */
+void efi_close_unsafe ( EFI_HANDLE handle, EFI_GUID *protocol ) {
+ EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+ EFI_HANDLE agent = efi_image_handle;
+ EFI_HANDLE controller;
+
+ /* Sanity checks */
+ assert ( handle != NULL );
+ assert ( protocol != NULL );
+
+ /* Close protocol */
+ controller = agent;
+ bs->CloseProtocol ( handle, protocol, agent, controller );
+}
+
+/**
+ * Open protocol for persistent use by a driver
+ *
+ * @v handle EFI handle
+ * @v protocol Protocol GUID
+ * @v interface Protocol interface pointer to fill in
+ * @ret rc Return status code
+ */
+int efi_open_by_driver ( EFI_HANDLE handle, EFI_GUID *protocol,
+ void **interface ) {
+ EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+ EFI_HANDLE agent = efi_image_handle;
+ EFI_HANDLE controller;
+ unsigned int attributes;
+ EFI_STATUS efirc;
+ int rc;
+
+ /* Sanity checks */
+ assert ( handle != NULL );
+ assert ( protocol != NULL );
+ assert ( interface != NULL );
+
+ /* Open protocol
+ *
+ * We set ControllerHandle equal to Handle to avoid collisions
+ * with other open types.
+ */
+ controller = handle;
+ attributes = ( EFI_OPEN_PROTOCOL_BY_DRIVER |
+ EFI_OPEN_PROTOCOL_EXCLUSIVE );
+ if ( ( efirc = bs->OpenProtocol ( handle, protocol, interface, agent,
+ controller, attributes ) ) != 0 ) {
+ rc = -EEFI ( efirc );
+ *interface = NULL;
+ return rc;
+ }
+
+ return 0;
+}
+
+/**
+ * Close protocol opened for persistent use by a driver
+ *
+ * @v handle EFI handle
+ * @v protocol Protocol GUID
+ */
+void efi_close_by_driver ( EFI_HANDLE handle, EFI_GUID *protocol ) {
+ EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+ EFI_HANDLE agent = efi_image_handle;
+ EFI_HANDLE controller;
+
+ /* Sanity checks */
+ assert ( handle != NULL );
+ assert ( protocol != NULL );
+
+ /* Close protocol */
+ controller = handle;
+ bs->CloseProtocol ( handle, protocol, agent, controller );
+}
+
+/**
+ * Open protocol for persistent use by a child controller
+ *
+ * @v handle EFI handle
+ * @v protocol Protocol GUID
+ * @v child Child controller handle
+ * @v interface Protocol interface pointer to fill in
+ * @ret rc Return status code
+ */
+int efi_open_by_child ( EFI_HANDLE handle, EFI_GUID *protocol,
+ EFI_HANDLE child, void **interface ) {
+ EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+ EFI_HANDLE agent = efi_image_handle;
+ EFI_HANDLE controller;
+ unsigned int attributes;
+ EFI_STATUS efirc;
+ int rc;
+
+ /* Sanity checks */
+ assert ( handle != NULL );
+ assert ( protocol != NULL );
+ assert ( child != NULL );
+ assert ( interface != NULL );
+
+ /* Open protocol
+ *
+ * We set ControllerHandle to a non-NULL value distinct from
+ * both Handle and AgentHandle to avoid collisions with other
+ * open types.
+ */
+ controller = child;
+ assert ( controller != handle );
+ assert ( controller != agent );
+ attributes = EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER;
+ if ( ( efirc = bs->OpenProtocol ( handle, protocol, interface, agent,
+ controller, attributes ) ) != 0 ) {
+ rc = -EEFI ( efirc );
+ *interface = NULL;
+ return rc;
+ }
+
+ return 0;
+}
+
+/**
+ * Close protocol opened for persistent use by a child controller
+ *
+ * @v handle EFI handle
+ * @v protocol Protocol GUID
+ * @v child Child controller handle
+ */
+void efi_close_by_child ( EFI_HANDLE handle, EFI_GUID *protocol,
+ EFI_HANDLE child ) {
+ EFI_BOOT_SERVICES *bs = efi_systab->BootServices;
+ EFI_HANDLE agent = efi_image_handle;
+ EFI_HANDLE controller;
+
+ /* Sanity checks */
+ assert ( handle != NULL );
+ assert ( protocol != NULL );
+ assert ( child != NULL );
+
+ /* Close protocol */
+ controller = child;
+ assert ( controller != handle );
+ assert ( controller != agent );
+ bs->CloseProtocol ( handle, protocol, agent, controller );
+}