| /* |
| * 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_untyped ( 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_untyped ( 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_untyped ( 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_untyped ( 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 ); |
| } |