mirror of https://github.com/ipxe/ipxe.git
359 lines
11 KiB
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 );
|
|
}
|