| /* |
| * Copyright (C) 2012 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 SNP HII protocol |
| * |
| * The HII protocols are some of the less-well designed parts of the |
| * entire EFI specification. This is a significant accomplishment. |
| * |
| * The face-slappingly ludicrous query string syntax seems to be |
| * motivated by the desire to allow a caller to query multiple drivers |
| * simultaneously via the single-instance HII_CONFIG_ROUTING_PROTOCOL, |
| * which is supposed to pass relevant subsets of the query string to |
| * the relevant drivers. |
| * |
| * Nobody uses the HII_CONFIG_ROUTING_PROTOCOL. Not even the EFI |
| * setup browser uses the HII_CONFIG_ROUTING_PROTOCOL. To the best of |
| * my knowledge, there has only ever been one implementation of the |
| * HII_CONFIG_ROUTING_PROTOCOL (as part of EDK2), and it just doesn't |
| * work. It's so badly broken that I can't even figure out what the |
| * code is _trying_ to do. |
| * |
| * Fundamentally, the problem seems to be that Javascript programmers |
| * should not be allowed to design APIs for C code. |
| */ |
| |
| #include <string.h> |
| #include <strings.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <wchar.h> |
| #include <errno.h> |
| #include <ipxe/settings.h> |
| #include <ipxe/nvo.h> |
| #include <ipxe/device.h> |
| #include <ipxe/netdevice.h> |
| #include <ipxe/version.h> |
| #include <ipxe/efi/efi.h> |
| #include <ipxe/efi/efi_hii.h> |
| #include <ipxe/efi/efi_snp.h> |
| #include <ipxe/efi/efi_strings.h> |
| #include <ipxe/efi/efi_path.h> |
| #include <ipxe/efi/efi_utils.h> |
| #include <ipxe/efi/efi_null.h> |
| #include <config/branding.h> |
| |
| /** EFI platform setup formset GUID */ |
| static EFI_GUID efi_hii_platform_setup_formset_guid |
| = EFI_HII_PLATFORM_SETUP_FORMSET_GUID; |
| |
| /** EFI IBM UCM compliant formset GUID */ |
| static EFI_GUID efi_hii_ibm_ucm_compliant_formset_guid |
| = EFI_HII_IBM_UCM_COMPLIANT_FORMSET_GUID; |
| |
| /** EFI HII database protocol */ |
| static EFI_HII_DATABASE_PROTOCOL *efihii; |
| EFI_REQUEST_PROTOCOL ( EFI_HII_DATABASE_PROTOCOL, &efihii ); |
| |
| /** |
| * Identify settings to be exposed via HII |
| * |
| * @v snpdev SNP device |
| * @ret settings Settings, or NULL |
| */ |
| static struct settings * efi_snp_hii_settings ( struct efi_snp_device *snpdev ){ |
| |
| return find_child_settings ( netdev_settings ( snpdev->netdev ), |
| NVO_SETTINGS_NAME ); |
| } |
| |
| /** |
| * Check whether or not setting is applicable |
| * |
| * @v snpdev SNP device |
| * @v setting Setting |
| * @ret applies Setting applies |
| */ |
| static int efi_snp_hii_setting_applies ( struct efi_snp_device *snpdev, |
| struct setting *setting ) { |
| |
| return nvo_applies ( efi_snp_hii_settings ( snpdev ), setting ); |
| } |
| |
| /** |
| * Generate a random GUID |
| * |
| * @v guid GUID to fill in |
| */ |
| static void efi_snp_hii_random_guid ( EFI_GUID *guid ) { |
| uint8_t *byte = ( ( uint8_t * ) guid ); |
| unsigned int i; |
| |
| for ( i = 0 ; i < sizeof ( *guid ) ; i++ ) |
| *(byte++) = random(); |
| } |
| |
| /** |
| * Generate EFI SNP questions |
| * |
| * @v snpdev SNP device |
| * @v ifr IFR builder |
| * @v varstore_id Variable store identifier |
| */ |
| static void efi_snp_hii_questions ( struct efi_snp_device *snpdev, |
| struct efi_ifr_builder *ifr, |
| unsigned int varstore_id ) { |
| struct setting *setting; |
| struct setting *previous = NULL; |
| unsigned int name_id; |
| unsigned int prompt_id; |
| unsigned int help_id; |
| unsigned int question_id; |
| |
| /* Add all applicable settings */ |
| for_each_table_entry ( setting, SETTINGS ) { |
| if ( ! efi_snp_hii_setting_applies ( snpdev, setting ) ) |
| continue; |
| if ( previous && ( setting_cmp ( setting, previous ) == 0 ) ) |
| continue; |
| previous = setting; |
| name_id = efi_ifr_string ( ifr, "%s", setting->name ); |
| prompt_id = efi_ifr_string ( ifr, "%s", setting->description ); |
| help_id = efi_ifr_string ( ifr, PRODUCT_SETTING_URI, |
| setting->name ); |
| question_id = setting->tag; |
| efi_ifr_string_op ( ifr, prompt_id, help_id, |
| question_id, varstore_id, name_id, |
| 0, 0x00, 0xff, 0 ); |
| } |
| } |
| |
| /** |
| * Build HII package list for SNP device |
| * |
| * @v snpdev SNP device |
| * @ret package Package list, or NULL on error |
| */ |
| static EFI_HII_PACKAGE_LIST_HEADER * |
| efi_snp_hii_package_list ( struct efi_snp_device *snpdev ) { |
| struct net_device *netdev = snpdev->netdev; |
| struct device *dev = netdev->dev; |
| struct efi_ifr_builder ifr; |
| EFI_HII_PACKAGE_LIST_HEADER *package; |
| const char *name; |
| EFI_GUID package_guid; |
| EFI_GUID formset_guid; |
| EFI_GUID varstore_guid; |
| unsigned int title_id; |
| unsigned int varstore_id; |
| |
| /* Initialise IFR builder */ |
| efi_ifr_init ( &ifr ); |
| |
| /* Determine product name */ |
| name = ( product_name[0] ? product_name : product_short_name ); |
| |
| /* Generate GUIDs */ |
| efi_snp_hii_random_guid ( &package_guid ); |
| efi_snp_hii_random_guid ( &formset_guid ); |
| efi_snp_hii_random_guid ( &varstore_guid ); |
| |
| /* Generate title string (used more than once) */ |
| title_id = efi_ifr_string ( &ifr, "%s (%s)", name, |
| netdev_addr ( netdev ) ); |
| |
| /* Generate opcodes */ |
| efi_ifr_form_set_op ( &ifr, &formset_guid, title_id, |
| efi_ifr_string ( &ifr, "Configure %s", |
| product_short_name ), |
| &efi_hii_platform_setup_formset_guid, |
| &efi_hii_ibm_ucm_compliant_formset_guid, NULL ); |
| efi_ifr_guid_class_op ( &ifr, EFI_NETWORK_DEVICE_CLASS ); |
| efi_ifr_guid_subclass_op ( &ifr, 0x03 ); |
| varstore_id = efi_ifr_varstore_name_value_op ( &ifr, &varstore_guid ); |
| efi_ifr_form_op ( &ifr, title_id ); |
| efi_ifr_text_op ( &ifr, |
| efi_ifr_string ( &ifr, "Name" ), |
| efi_ifr_string ( &ifr, "Firmware product name" ), |
| efi_ifr_string ( &ifr, "%s", name ) ); |
| efi_ifr_text_op ( &ifr, |
| efi_ifr_string ( &ifr, "Version" ), |
| efi_ifr_string ( &ifr, "Firmware version" ), |
| efi_ifr_string ( &ifr, "%s", product_version ) ); |
| efi_ifr_text_op ( &ifr, |
| efi_ifr_string ( &ifr, "Driver" ), |
| efi_ifr_string ( &ifr, "Firmware driver" ), |
| efi_ifr_string ( &ifr, "%s", dev->driver_name ) ); |
| efi_ifr_text_op ( &ifr, |
| efi_ifr_string ( &ifr, "Device" ), |
| efi_ifr_string ( &ifr, "Hardware device" ), |
| efi_ifr_string ( &ifr, "%s", dev->name ) ); |
| efi_snp_hii_questions ( snpdev, &ifr, varstore_id ); |
| efi_ifr_end_op ( &ifr ); |
| efi_ifr_end_op ( &ifr ); |
| |
| /* Build package */ |
| package = efi_ifr_package ( &ifr, &package_guid, "en-us", |
| efi_ifr_string ( &ifr, "English" ) ); |
| if ( ! package ) { |
| DBGC ( snpdev, "SNPDEV %p could not build IFR package\n", |
| snpdev ); |
| efi_ifr_free ( &ifr ); |
| return NULL; |
| } |
| |
| /* Free temporary storage */ |
| efi_ifr_free ( &ifr ); |
| return package; |
| } |
| |
| /** |
| * Append response to result string |
| * |
| * @v snpdev SNP device |
| * @v key Key |
| * @v value Value |
| * @v results Result string |
| * @ret rc Return status code |
| * |
| * The result string is allocated dynamically using |
| * BootServices::AllocatePool(), and the caller is responsible for |
| * eventually calling BootServices::FreePool(). |
| */ |
| static int efi_snp_hii_append ( struct efi_snp_device *snpdev __unused, |
| const char *key, const char *value, |
| wchar_t **results ) { |
| EFI_BOOT_SERVICES *bs = efi_systab->BootServices; |
| EFI_STATUS efirc; |
| size_t len; |
| void *new; |
| |
| /* Allocate new string */ |
| len = ( ( *results ? ( wcslen ( *results ) + 1 /* "&" */ ) : 0 ) + |
| strlen ( key ) + 1 /* "=" */ + strlen ( value ) + 1 /* NUL */ ); |
| if ( ( efirc = bs->AllocatePool ( EfiBootServicesData, |
| ( len * sizeof ( wchar_t ) ), |
| &new ) ) != 0 ) |
| return -EEFI ( efirc ); |
| |
| /* Populate string */ |
| efi_snprintf ( new, len, "%ls%s%s=%s", ( *results ? *results : L"" ), |
| ( *results ? L"&" : L"" ), key, value ); |
| bs->FreePool ( *results ); |
| *results = new; |
| |
| return 0; |
| } |
| |
| /** |
| * Fetch HII setting |
| * |
| * @v snpdev SNP device |
| * @v key Key |
| * @v value Value |
| * @v results Result string |
| * @v have_setting Flag indicating detection of a setting |
| * @ret rc Return status code |
| */ |
| static int efi_snp_hii_fetch ( struct efi_snp_device *snpdev, |
| const char *key, const char *value, |
| wchar_t **results, int *have_setting ) { |
| struct settings *settings = efi_snp_hii_settings ( snpdev ); |
| struct settings *origin; |
| struct setting *setting; |
| struct setting fetched; |
| int len; |
| char *buf; |
| char *encoded; |
| int i; |
| int rc; |
| |
| /* Handle ConfigHdr components */ |
| if ( ( strcasecmp ( key, "GUID" ) == 0 ) || |
| ( strcasecmp ( key, "NAME" ) == 0 ) || |
| ( strcasecmp ( key, "PATH" ) == 0 ) ) { |
| return efi_snp_hii_append ( snpdev, key, value, results ); |
| } |
| if ( have_setting ) |
| *have_setting = 1; |
| |
| /* Do nothing more unless we have a settings block */ |
| if ( ! settings ) { |
| rc = -ENOTSUP; |
| goto err_no_settings; |
| } |
| |
| /* Identify setting */ |
| setting = find_setting ( key ); |
| if ( ! setting ) { |
| DBGC ( snpdev, "SNPDEV %p no such setting \"%s\"\n", |
| snpdev, key ); |
| rc = -ENODEV; |
| goto err_find_setting; |
| } |
| |
| /* Encode value */ |
| if ( setting_exists ( settings, setting ) ) { |
| |
| /* Calculate formatted length */ |
| len = fetchf_setting ( settings, setting, &origin, &fetched, |
| NULL, 0 ); |
| if ( len < 0 ) { |
| rc = len; |
| DBGC ( snpdev, "SNPDEV %p could not fetch %s: %s\n", |
| snpdev, setting->name, strerror ( rc ) ); |
| goto err_fetchf_len; |
| } |
| |
| /* Allocate buffer for formatted value and HII-encoded value */ |
| buf = zalloc ( len + 1 /* NUL */ + ( len * 4 ) + 1 /* NUL */ ); |
| if ( ! buf ) { |
| rc = -ENOMEM; |
| goto err_alloc; |
| } |
| encoded = ( buf + len + 1 /* NUL */ ); |
| |
| /* Format value */ |
| fetchf_setting ( origin, &fetched, NULL, NULL, buf, |
| ( len + 1 /* NUL */ ) ); |
| for ( i = 0 ; i < len ; i++ ) { |
| sprintf ( ( encoded + ( 4 * i ) ), "%04x", |
| *( ( uint8_t * ) buf + i ) ); |
| } |
| |
| } else { |
| |
| /* Non-existent or inapplicable setting */ |
| buf = NULL; |
| encoded = ""; |
| } |
| |
| /* Append results */ |
| if ( ( rc = efi_snp_hii_append ( snpdev, key, encoded, |
| results ) ) != 0 ) { |
| goto err_append; |
| } |
| |
| /* Success */ |
| rc = 0; |
| |
| err_append: |
| free ( buf ); |
| err_alloc: |
| err_fetchf_len: |
| err_find_setting: |
| err_no_settings: |
| return rc; |
| } |
| |
| /** |
| * Fetch HII setting |
| * |
| * @v snpdev SNP device |
| * @v key Key |
| * @v value Value |
| * @v results Result string (unused) |
| * @v have_setting Flag indicating detection of a setting (unused) |
| * @ret rc Return status code |
| */ |
| static int efi_snp_hii_store ( struct efi_snp_device *snpdev, |
| const char *key, const char *value, |
| wchar_t **results __unused, |
| int *have_setting __unused ) { |
| struct settings *settings = efi_snp_hii_settings ( snpdev ); |
| struct setting *setting; |
| char *buf; |
| char tmp[5]; |
| char *endp; |
| int len; |
| int i; |
| int rc; |
| |
| /* Handle ConfigHdr components */ |
| if ( ( strcasecmp ( key, "GUID" ) == 0 ) || |
| ( strcasecmp ( key, "NAME" ) == 0 ) || |
| ( strcasecmp ( key, "PATH" ) == 0 ) ) { |
| /* Nothing to do */ |
| return 0; |
| } |
| |
| /* Do nothing more unless we have a settings block */ |
| if ( ! settings ) { |
| rc = -ENOTSUP; |
| goto err_no_settings; |
| } |
| |
| /* Identify setting */ |
| setting = find_setting ( key ); |
| if ( ! setting ) { |
| DBGC ( snpdev, "SNPDEV %p no such setting \"%s\"\n", |
| snpdev, key ); |
| rc = -ENODEV; |
| goto err_find_setting; |
| } |
| |
| /* Allocate buffer */ |
| len = ( strlen ( value ) / 4 ); |
| buf = zalloc ( len + 1 /* NUL */ ); |
| if ( ! buf ) { |
| rc = -ENOMEM; |
| goto err_alloc; |
| } |
| |
| /* Decode value */ |
| tmp[4] = '\0'; |
| for ( i = 0 ; i < len ; i++ ) { |
| memcpy ( tmp, ( value + ( i * 4 ) ), 4 ); |
| buf[i] = strtoul ( tmp, &endp, 16 ); |
| if ( endp != &tmp[4] ) { |
| DBGC ( snpdev, "SNPDEV %p invalid character %s\n", |
| snpdev, tmp ); |
| rc = -EINVAL; |
| goto err_inval; |
| } |
| } |
| |
| /* Store value */ |
| if ( ( rc = storef_setting ( settings, setting, buf ) ) != 0 ) { |
| DBGC ( snpdev, "SNPDEV %p could not store \"%s\" into %s: %s\n", |
| snpdev, buf, setting->name, strerror ( rc ) ); |
| goto err_storef; |
| } |
| |
| /* Success */ |
| rc = 0; |
| |
| err_storef: |
| err_inval: |
| free ( buf ); |
| err_alloc: |
| err_find_setting: |
| err_no_settings: |
| return rc; |
| } |
| |
| /** |
| * Process portion of HII configuration string |
| * |
| * @v snpdev SNP device |
| * @v string HII configuration string |
| * @v progress Progress through HII configuration string |
| * @v results Results string |
| * @v have_setting Flag indicating detection of a setting (unused) |
| * @v process Function used to process key=value pairs |
| * @ret rc Return status code |
| */ |
| static int efi_snp_hii_process ( struct efi_snp_device *snpdev, |
| wchar_t *string, wchar_t **progress, |
| wchar_t **results, int *have_setting, |
| int ( * process ) ( struct efi_snp_device *, |
| const char *key, |
| const char *value, |
| wchar_t **results, |
| int *have_setting ) ) { |
| wchar_t *wkey = string; |
| wchar_t *wend = string; |
| wchar_t *wvalue = NULL; |
| size_t key_len; |
| size_t value_len; |
| void *temp; |
| char *key; |
| char *value; |
| int rc; |
| |
| /* Locate key, value (if any), and end */ |
| while ( *wend ) { |
| if ( *wend == L'&' ) |
| break; |
| if ( *(wend++) == L'=' ) |
| wvalue = wend; |
| } |
| |
| /* Allocate memory for key and value */ |
| key_len = ( ( wvalue ? ( wvalue - 1 ) : wend ) - wkey ); |
| value_len = ( wvalue ? ( wend - wvalue ) : 0 ); |
| temp = zalloc ( key_len + 1 /* NUL */ + value_len + 1 /* NUL */ ); |
| if ( ! temp ) |
| return -ENOMEM; |
| key = temp; |
| value = ( temp + key_len + 1 /* NUL */ ); |
| |
| /* Copy key and value */ |
| while ( key_len-- ) |
| key[key_len] = wkey[key_len]; |
| while ( value_len-- ) |
| value[value_len] = wvalue[value_len]; |
| |
| /* Process key and value */ |
| if ( ( rc = process ( snpdev, key, value, results, |
| have_setting ) ) != 0 ) { |
| goto err; |
| } |
| |
| /* Update progress marker */ |
| *progress = wend; |
| |
| err: |
| /* Free temporary storage */ |
| free ( temp ); |
| |
| return rc; |
| } |
| |
| /** |
| * Fetch configuration |
| * |
| * @v hii HII configuration access protocol |
| * @v request Configuration to fetch |
| * @ret progress Progress made through configuration to fetch |
| * @ret results Query results |
| * @ret efirc EFI status code |
| */ |
| static EFI_STATUS EFIAPI |
| efi_snp_hii_extract_config ( const EFI_HII_CONFIG_ACCESS_PROTOCOL *hii, |
| EFI_STRING request, EFI_STRING *progress, |
| EFI_STRING *results ) { |
| struct efi_snp_device *snpdev = |
| container_of ( hii, struct efi_snp_device, hii ); |
| int have_setting = 0; |
| wchar_t *pos; |
| int rc; |
| |
| DBGC ( snpdev, "SNPDEV %p ExtractConfig request \"%ls\"\n", |
| snpdev, request ); |
| |
| /* Initialise results */ |
| *results = NULL; |
| |
| /* Work around apparently broken UEFI specification */ |
| if ( ! ( request && request[0] ) ) { |
| DBGC ( snpdev, "SNPDEV %p ExtractConfig ignoring malformed " |
| "request\n", snpdev ); |
| return EFI_INVALID_PARAMETER; |
| } |
| |
| /* Process all request fragments */ |
| for ( pos = *progress = request ; *progress && **progress ; |
| pos = *progress + 1 ) { |
| if ( ( rc = efi_snp_hii_process ( snpdev, pos, progress, |
| results, &have_setting, |
| efi_snp_hii_fetch ) ) != 0 ) { |
| return EFIRC ( rc ); |
| } |
| } |
| |
| /* If we have no explicit request, return all settings */ |
| if ( ! have_setting ) { |
| struct setting *setting; |
| |
| for_each_table_entry ( setting, SETTINGS ) { |
| if ( ! efi_snp_hii_setting_applies ( snpdev, setting ) ) |
| continue; |
| if ( ( rc = efi_snp_hii_fetch ( snpdev, setting->name, |
| NULL, results, |
| NULL ) ) != 0 ) { |
| return EFIRC ( rc ); |
| } |
| } |
| } |
| |
| DBGC ( snpdev, "SNPDEV %p ExtractConfig results \"%ls\"\n", |
| snpdev, *results ); |
| return 0; |
| } |
| |
| /** |
| * Store configuration |
| * |
| * @v hii HII configuration access protocol |
| * @v config Configuration to store |
| * @ret progress Progress made through configuration to store |
| * @ret efirc EFI status code |
| */ |
| static EFI_STATUS EFIAPI |
| efi_snp_hii_route_config ( const EFI_HII_CONFIG_ACCESS_PROTOCOL *hii, |
| EFI_STRING config, EFI_STRING *progress ) { |
| struct efi_snp_device *snpdev = |
| container_of ( hii, struct efi_snp_device, hii ); |
| wchar_t *pos; |
| int rc; |
| |
| DBGC ( snpdev, "SNPDEV %p RouteConfig \"%ls\"\n", snpdev, config ); |
| |
| /* Process all request fragments */ |
| for ( pos = *progress = config ; *progress && **progress ; |
| pos = *progress + 1 ) { |
| if ( ( rc = efi_snp_hii_process ( snpdev, pos, progress, |
| NULL, NULL, |
| efi_snp_hii_store ) ) != 0 ) { |
| return EFIRC ( rc ); |
| } |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * Handle form actions |
| * |
| * @v hii HII configuration access protocol |
| * @v action Form browser action |
| * @v question_id Question ID |
| * @v type Type of value |
| * @v value Value |
| * @ret action_request Action requested by driver |
| * @ret efirc EFI status code |
| */ |
| static EFI_STATUS EFIAPI |
| efi_snp_hii_callback ( const EFI_HII_CONFIG_ACCESS_PROTOCOL *hii, |
| EFI_BROWSER_ACTION action __unused, |
| EFI_QUESTION_ID question_id __unused, |
| UINT8 type __unused, EFI_IFR_TYPE_VALUE *value __unused, |
| EFI_BROWSER_ACTION_REQUEST *action_request __unused ) { |
| struct efi_snp_device *snpdev = |
| container_of ( hii, struct efi_snp_device, hii ); |
| |
| DBGC ( snpdev, "SNPDEV %p Callback\n", snpdev ); |
| return EFI_UNSUPPORTED; |
| } |
| |
| /** HII configuration access protocol */ |
| static EFI_HII_CONFIG_ACCESS_PROTOCOL efi_snp_device_hii = { |
| .ExtractConfig = efi_snp_hii_extract_config, |
| .RouteConfig = efi_snp_hii_route_config, |
| .Callback = efi_snp_hii_callback, |
| }; |
| |
| /** |
| * Install HII protocol and packages for SNP device |
| * |
| * @v snpdev SNP device |
| * @ret rc Return status code |
| */ |
| int efi_snp_hii_install ( struct efi_snp_device *snpdev ) { |
| EFI_BOOT_SERVICES *bs = efi_systab->BootServices; |
| VENDOR_DEVICE_PATH *vendor_path; |
| EFI_DEVICE_PATH_PROTOCOL *path_end; |
| size_t path_prefix_len; |
| int leak = 0; |
| EFI_STATUS efirc; |
| int rc; |
| |
| /* Do nothing if HII database protocol is not supported */ |
| if ( ! efihii ) { |
| rc = -ENOTSUP; |
| goto err_no_hii; |
| } |
| |
| /* Initialise HII protocol */ |
| memcpy ( &snpdev->hii, &efi_snp_device_hii, sizeof ( snpdev->hii ) ); |
| |
| /* Create HII package list */ |
| snpdev->package_list = efi_snp_hii_package_list ( snpdev ); |
| if ( ! snpdev->package_list ) { |
| DBGC ( snpdev, "SNPDEV %p could not create HII package list\n", |
| snpdev ); |
| rc = -ENOMEM; |
| goto err_build_package_list; |
| } |
| |
| /* Allocate the new device path */ |
| path_prefix_len = efi_path_len ( snpdev->path ); |
| snpdev->hii_child_path = zalloc ( path_prefix_len + |
| sizeof ( *vendor_path ) + |
| sizeof ( *path_end ) ); |
| if ( ! snpdev->hii_child_path ) { |
| DBGC ( snpdev, |
| "SNPDEV %p could not allocate HII child device path\n", |
| snpdev ); |
| rc = -ENOMEM; |
| goto err_alloc_child_path; |
| } |
| |
| /* Populate the device path */ |
| memcpy ( snpdev->hii_child_path, snpdev->path, path_prefix_len ); |
| vendor_path = ( ( ( void * ) snpdev->hii_child_path ) + |
| path_prefix_len ); |
| vendor_path->Header.Type = HARDWARE_DEVICE_PATH; |
| vendor_path->Header.SubType = HW_VENDOR_DP; |
| vendor_path->Header.Length[0] = sizeof ( *vendor_path ); |
| efi_snp_hii_random_guid ( &vendor_path->Guid ); |
| path_end = ( ( void * ) ( vendor_path + 1 ) ); |
| path_end->Type = END_DEVICE_PATH_TYPE; |
| path_end->SubType = END_ENTIRE_DEVICE_PATH_SUBTYPE; |
| path_end->Length[0] = sizeof ( *path_end ); |
| |
| /* Create device path and child handle for HII association */ |
| if ( ( efirc = bs->InstallMultipleProtocolInterfaces ( |
| &snpdev->hii_child_handle, |
| &efi_device_path_protocol_guid, snpdev->hii_child_path, |
| NULL ) ) != 0 ) { |
| rc = -EEFI ( efirc ); |
| DBGC ( snpdev, "SNPDEV %p could not create HII child handle: " |
| "%s\n", snpdev, strerror ( rc ) ); |
| goto err_hii_child_handle; |
| } |
| |
| /* Add HII packages */ |
| if ( ( efirc = efihii->NewPackageList ( efihii, snpdev->package_list, |
| snpdev->hii_child_handle, |
| &snpdev->hii_handle ) ) != 0 ) { |
| rc = -EEFI ( efirc ); |
| DBGC ( snpdev, "SNPDEV %p could not add HII packages: %s\n", |
| snpdev, strerror ( rc ) ); |
| goto err_new_package_list; |
| } |
| |
| /* Install HII protocol */ |
| if ( ( efirc = bs->InstallMultipleProtocolInterfaces ( |
| &snpdev->hii_child_handle, |
| &efi_hii_config_access_protocol_guid, &snpdev->hii, |
| NULL ) ) != 0 ) { |
| rc = -EEFI ( efirc ); |
| DBGC ( snpdev, "SNPDEV %p could not install HII protocol: %s\n", |
| snpdev, strerror ( rc ) ); |
| goto err_install_protocol; |
| } |
| |
| /* Add as child of handle with SNP instance */ |
| if ( ( rc = efi_child_add ( snpdev->handle, |
| snpdev->hii_child_handle ) ) != 0 ) { |
| DBGC ( snpdev, |
| "SNPDEV %p could not adopt HII child handle: %s\n", |
| snpdev, strerror ( rc ) ); |
| goto err_efi_child_add; |
| } |
| |
| return 0; |
| |
| efi_child_del ( snpdev->handle, snpdev->hii_child_handle ); |
| err_efi_child_add: |
| if ( ( efirc = bs->UninstallMultipleProtocolInterfaces ( |
| snpdev->hii_child_handle, |
| &efi_hii_config_access_protocol_guid, &snpdev->hii, |
| NULL ) ) != 0 ) { |
| DBGC ( snpdev, "SNPDEV %p could not uninstall HII protocol: " |
| "%s\n", snpdev, strerror ( -EEFI ( efirc ) ) ); |
| leak = 1; |
| } |
| efi_nullify_hii ( &snpdev->hii ); |
| err_install_protocol: |
| if ( ! leak ) |
| efihii->RemovePackageList ( efihii, snpdev->hii_handle ); |
| err_new_package_list: |
| if ( ( efirc = bs->UninstallMultipleProtocolInterfaces ( |
| snpdev->hii_child_handle, |
| &efi_device_path_protocol_guid, snpdev->hii_child_path, |
| NULL ) ) != 0 ) { |
| DBGC ( snpdev, "SNPDEV %p could not uninstall HII path: %s\n", |
| snpdev, strerror ( -EEFI ( efirc ) ) ); |
| leak = 1; |
| } |
| err_hii_child_handle: |
| if ( ! leak ) { |
| free ( snpdev->hii_child_path ); |
| snpdev->hii_child_path = NULL; |
| } |
| err_alloc_child_path: |
| if ( ! leak ) { |
| free ( snpdev->package_list ); |
| snpdev->package_list = NULL; |
| } |
| err_build_package_list: |
| err_no_hii: |
| return rc; |
| } |
| |
| /** |
| * Uninstall HII protocol and package for SNP device |
| * |
| * @v snpdev SNP device |
| * @ret leak Uninstallation failed: leak memory |
| */ |
| int efi_snp_hii_uninstall ( struct efi_snp_device *snpdev ) { |
| EFI_BOOT_SERVICES *bs = efi_systab->BootServices; |
| int leak = efi_shutdown_in_progress; |
| EFI_STATUS efirc; |
| |
| /* Do nothing if HII database protocol is not supported */ |
| if ( ! efihii ) |
| return 0; |
| |
| /* Uninstall protocols and remove package list */ |
| efi_child_del ( snpdev->handle, snpdev->hii_child_handle ); |
| if ( ( ! efi_shutdown_in_progress ) && |
| ( ( efirc = bs->UninstallMultipleProtocolInterfaces ( |
| snpdev->hii_child_handle, |
| &efi_hii_config_access_protocol_guid, &snpdev->hii, |
| NULL ) ) != 0 ) ) { |
| DBGC ( snpdev, "SNPDEV %p could not uninstall HII protocol: " |
| "%s\n", snpdev, strerror ( -EEFI ( efirc ) ) ); |
| leak = 1; |
| } |
| efi_nullify_hii ( &snpdev->hii ); |
| if ( ! leak ) |
| efihii->RemovePackageList ( efihii, snpdev->hii_handle ); |
| if ( ( ! efi_shutdown_in_progress ) && |
| ( ( efirc = bs->UninstallMultipleProtocolInterfaces ( |
| snpdev->hii_child_handle, |
| &efi_device_path_protocol_guid, snpdev->hii_child_path, |
| NULL ) ) != 0 ) ) { |
| DBGC ( snpdev, "SNPDEV %p could not uninstall HII path: %s\n", |
| snpdev, strerror ( -EEFI ( efirc ) ) ); |
| leak = 1; |
| } |
| if ( ! leak ) { |
| free ( snpdev->hii_child_path ); |
| snpdev->hii_child_path = NULL; |
| free ( snpdev->package_list ); |
| snpdev->package_list = NULL; |
| } |
| |
| /* Report leakage, if applicable */ |
| if ( leak && ( ! efi_shutdown_in_progress ) ) |
| DBGC ( snpdev, "SNPDEV %p HII nullified and leaked\n", snpdev ); |
| return leak; |
| } |