/* * Copyright (C) 2023 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 ); /** * @file * * EFI variable settings * */ #include #include #include #include #include #include #include /** EFI variable settings scope */ static const struct settings_scope efivars_scope; /** EFI variable settings */ static struct settings efivars; /** * Check applicability of EFI variable setting * * @v settings Settings block * @v setting Setting * @ret applies Setting applies within this settings block */ static int efivars_applies ( struct settings *settings __unused, const struct setting *setting ) { return ( setting->scope == &efivars_scope ); } /** * Find first matching EFI variable name * * @v wname Name * @v guid GUID to fill in * @ret rc Return status code */ static int efivars_find ( const CHAR16 *wname, EFI_GUID *guid ) { EFI_RUNTIME_SERVICES *rs = efi_systab->RuntimeServices; size_t wname_len = ( ( wcslen ( wname ) + 1 ) * sizeof ( wname[0] ) ); CHAR16 *buf; CHAR16 *tmp; UINTN size; EFI_STATUS efirc; int rc; /* Allocate single wNUL for first call to GetNextVariableName() */ size = sizeof ( buf[0] ); buf = zalloc ( size ); if ( ! buf ) return -ENOMEM; /* Iterate over all veriables */ while ( 1 ) { /* Get next variable name */ efirc = rs->GetNextVariableName ( &size, buf, guid ); if ( efirc == EFI_BUFFER_TOO_SMALL ) { tmp = realloc ( buf, size ); if ( ! tmp ) { rc = -ENOMEM; break; } buf = tmp; efirc = rs->GetNextVariableName ( &size, buf, guid ); } if ( efirc == EFI_NOT_FOUND ) { rc = -ENOENT; break; } if ( efirc != 0 ) { rc = -EEFI ( efirc ); DBGC ( &efivars, "EFIVARS %s:%ls could not fetch next " "variable name: %s\n", efi_guid_ntoa ( guid ), buf, strerror ( rc ) ); break; } DBGC2 ( &efivars, "EFIVARS %s:%ls exists\n", efi_guid_ntoa ( guid ), buf ); /* Check for matching variable name */ if ( memcmp ( wname, buf, wname_len ) == 0 ) { rc = 0; break; } } /* Free temporary buffer */ free ( buf ); return rc; } /** * Fetch value of EFI variable setting * * @v settings Settings block * @v setting Setting to fetch * @v data Buffer to fill with setting data * @v len Length of buffer * @ret len Length of setting data, or negative error */ static int efivars_fetch ( struct settings *settings __unused, struct setting *setting, void *data, size_t len ) { EFI_RUNTIME_SERVICES *rs = efi_systab->RuntimeServices; size_t name_len = strlen ( setting->name ); CHAR16 wname[ name_len + 1 /* wNUL */ ]; EFI_GUID guid; UINT32 attrs; UINTN size; void *buf; EFI_STATUS efirc; int rc; /* Convert name to UCS-2 */ efi_snprintf ( wname, sizeof ( wname ), "%s", setting->name ); /* Find variable GUID */ if ( ( rc = efivars_find ( wname, &guid ) ) != 0 ) goto err_find; /* Get variable length */ size = 0; if ( ( efirc = rs->GetVariable ( wname, &guid, &attrs, &size, NULL ) != EFI_BUFFER_TOO_SMALL ) ) { rc = -EEFI ( efirc ); DBGC ( &efivars, "EFIVARS %s:%ls could not get size: %s\n", efi_guid_ntoa ( &guid ), wname, strerror ( rc ) ); goto err_len; } /* Allocate temporary buffer, since GetVariable() is not * guaranteed to return partial data for an underlength * buffer. */ buf = malloc ( size ); if ( ! buf ) { rc = -ENOMEM; goto err_alloc; } /* Get variable value */ if ( ( efirc = rs->GetVariable ( wname, &guid, &attrs, &size, buf ) ) != 0 ) { rc = -EEFI ( efirc ); DBGC ( &efivars, "EFIVARS %s:%ls could not get %zd bytes: " "%s\n", efi_guid_ntoa ( &guid ), wname, ( ( size_t ) size ), strerror ( rc ) ); goto err_get; } DBGC ( &efivars, "EFIVARS %s:%ls:\n", efi_guid_ntoa ( &guid ), wname ); DBGC_HDA ( &efivars, 0, buf, size ); /* Return setting value */ if ( len > size ) len = size; memcpy ( data, buf, len ); if ( ! setting->type ) setting->type = &setting_type_hex; /* Free temporary buffer */ free ( buf ); return size; err_get: free ( buf ); err_alloc: err_len: err_find: return rc; } /** EFI variable settings operations */ static struct settings_operations efivars_operations = { .applies = efivars_applies, .fetch = efivars_fetch, }; /** EFI variable settings */ static struct settings efivars = { .refcnt = NULL, .siblings = LIST_HEAD_INIT ( efivars.siblings ), .children = LIST_HEAD_INIT ( efivars.children ), .op = &efivars_operations, .default_scope = &efivars_scope, }; /** * Initialise EFI variable settings * */ static void efivars_init ( void ) { int rc; /* Register settings block */ if ( ( rc = register_settings ( &efivars, NULL, "efi" ) ) != 0 ) { DBGC ( &efivars, "EFIVARS could not register: %s\n", strerror ( rc ) ); return; } } /** EFI variable settings initialiser */ struct init_fn efivars_init_fn __init_fn ( INIT_NORMAL ) = { .initialise = efivars_init, };