diff --git a/src/include/ipxe/efi/efi_table.h b/src/include/ipxe/efi/efi_table.h new file mode 100644 index 000000000..9a41d8723 --- /dev/null +++ b/src/include/ipxe/efi/efi_table.h @@ -0,0 +1,37 @@ +#ifndef _IPXE_EFI_TABLE_H +#define _IPXE_EFI_TABLE_H + +/** @file + * + * EFI configuration tables + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include + +/** An installable EFI configuration table type */ +struct efi_table { + /** Table GUID */ + EFI_GUID *guid; + /** + * Determine length of table + * + * @v data Configuration table data (presumed valid) + * @ret len Length of table + * + * EFI does not record the length of installed configuration + * tables. Consumers must understand the specific type of + * table in order to be able to determine its length from the + * contents. + */ + size_t ( * len ) ( const void *data ); +}; + +extern void * efi_find_table ( EFI_GUID *guid ); +extern int efi_install_table ( struct efi_table *table, const void *data, + void **backup ); +extern int efi_uninstall_table ( struct efi_table *table, void **backup ); + +#endif /* _IPXE_EFI_TABLE_H */ diff --git a/src/include/ipxe/errfile.h b/src/include/ipxe/errfile.h index 51b458d56..a64104a22 100644 --- a/src/include/ipxe/errfile.h +++ b/src/include/ipxe/errfile.h @@ -85,6 +85,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define ERRFILE_efi_service ( ERRFILE_CORE | 0x002d0000 ) #define ERRFILE_null_smbios ( ERRFILE_CORE | 0x002e0000 ) #define ERRFILE_efi_open ( ERRFILE_CORE | 0x002f0000 ) +#define ERRFILE_efi_table ( ERRFILE_CORE | 0x00300000 ) #define ERRFILE_eisa ( ERRFILE_DRIVER | 0x00000000 ) #define ERRFILE_isa ( ERRFILE_DRIVER | 0x00010000 ) diff --git a/src/interface/efi/efi_init.c b/src/interface/efi/efi_init.c index 1ba144ee7..69aea0d50 100644 --- a/src/interface/efi/efi_init.c +++ b/src/interface/efi/efi_init.c @@ -25,6 +25,7 @@ FILE_LICENCE ( GPL2_OR_LATER ); #include #include #include +#include #include #include #include @@ -103,24 +104,6 @@ static EFIAPI void efi_shutdown_hook ( EFI_EVENT event __unused, shutdown_boot(); } -/** - * Look up EFI configuration table - * - * @v guid Configuration table GUID - * @ret table Configuration table, or NULL - */ -static void * efi_find_table ( EFI_GUID *guid ) { - unsigned int i; - - for ( i = 0 ; i < efi_systab->NumberOfTableEntries ; i++ ) { - if ( memcmp ( &efi_systab->ConfigurationTable[i].VendorGuid, - guid, sizeof ( *guid ) ) == 0 ) - return efi_systab->ConfigurationTable[i].VendorTable; - } - - return NULL; -} - /** * Construct a stack cookie value * diff --git a/src/interface/efi/efi_table.c b/src/interface/efi/efi_table.c new file mode 100644 index 000000000..3c3f35a4b --- /dev/null +++ b/src/interface/efi/efi_table.c @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2025 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 ); + +#include +#include +#include +#include +#include + +/** @file + * + * EFI configuration tables + * + */ + +/** + * Look up EFI configuration table + * + * @v guid Configuration table GUID + * @ret table Configuration table, or NULL + */ +void * efi_find_table ( EFI_GUID *guid ) { + void *table; + unsigned int i; + + /* Scan for installed table */ + for ( i = 0 ; i < efi_systab->NumberOfTableEntries ; i++ ) { + if ( memcmp ( &efi_systab->ConfigurationTable[i].VendorGuid, + guid, sizeof ( *guid ) ) == 0 ) { + table = efi_systab->ConfigurationTable[i].VendorTable; + DBGC ( guid, "EFITAB %s is at %p\n", + efi_guid_ntoa ( guid ), table ); + return table; + } + } + + return NULL; +} + +/** + * Install EFI configuration table + * + * @v table Configuration table type + * @v data Configuration table data, or NULL to uninstall + * @v backup Table backup, or NULL to not back up old table + * @ret rc Return status code + */ +int efi_install_table ( struct efi_table *table, const void *data, + void **backup ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + EFI_GUID *guid = table->guid; + void *copy; + void *new; + void *old; + size_t old_len; + size_t new_len; + EFI_STATUS efirc; + int rc; + + /* Get currently installed table, if any */ + old = efi_find_table ( guid ); + old_len = ( old ? table->len ( old ) : 0 ); + + /* Create backup copy, if applicable */ + if ( old_len && backup ) { + if ( ( efirc = bs->AllocatePool ( EfiBootServicesData, old_len, + © ) ) != 0 ) { + rc = -EEFI ( efirc ); + goto err_backup; + } + memcpy ( copy, old, old_len ); + DBGC ( table, "EFITAB %s %p+%#zx backed up\n", + efi_guid_ntoa ( guid ), old, old_len ); + } else { + copy = NULL; + } + + /* Create installable runtime services data copy, if applicable */ + new_len = ( data ? table->len ( data ) : 0 ); + if ( new_len ) { + if ( ( efirc = bs->AllocatePool ( EfiRuntimeServicesData, + new_len, &new ) ) != 0 ) { + rc = -EEFI ( efirc ); + goto err_allocate; + } + memcpy ( new, data, new_len ); + } else { + new = NULL; + } + + /* (Un)install configuration table, if applicable */ + if ( new || old ) { + if ( ( efirc = bs->InstallConfigurationTable ( guid, + new ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( table, "EFITAB %s could not install: %s\n", + efi_guid_ntoa ( guid ), strerror ( rc ) ); + goto err_install; + } + if ( old ) { + DBGC ( table, "EFITAB %s %p+%#zx uninstalled\n", + efi_guid_ntoa ( guid ), old, old_len ); + } + if ( new ) { + DBGC ( table, "EFITAB %s %p+%#zx installed\n", + efi_guid_ntoa ( guid ), new, new_len ); + } + } + + /* Record backup copy, if applicable */ + if ( backup ) { + if ( *backup ) + bs->FreePool ( *backup ); + *backup = copy; + } + + /* Sanity check */ + assert ( efi_find_table ( guid ) == new ); + + return 0; + + err_install: + if ( new ) + bs->FreePool ( new ); + err_allocate: + if ( copy ) + bs->FreePool ( copy ); + err_backup: + return rc; +} + +/** + * Uninstall EFI configuration table + * + * @v table Configuration table type + * @v backup Table backup (or NULL to not restore old table) + * @ret rc Return status code + */ +int efi_uninstall_table ( struct efi_table *table, void **backup ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + void *old; + int rc; + + /* Uninstall or reinstall as applicable */ + old = ( backup ? *backup : NULL ); + if ( ( rc = efi_install_table ( table, old, NULL ) ) != 0 ) + return rc; + + /* Free backup copy, if applicable */ + if ( backup && *backup ) { + bs->FreePool ( *backup ); + *backup = NULL; + } + + return 0; +}