opengnsys_ipxe/src/interface/efi/efi_open.c

359 lines
11 KiB
C

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