From a4d125081010ff9816add31af2f732e6ec22a704 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Thu, 11 Oct 2012 15:02:50 +0100 Subject: [PATCH] [efi] Expose net device non-volatile settings via HII Signed-off-by: Michael Brown --- src/include/ipxe/efi/efi_hii.h | 203 ++++----- src/interface/efi/efi_hii.c | 577 +++++++++++++++++++++++++ src/interface/efi/efi_snp_hii.c | 730 ++++++++++++++++++++++---------- 3 files changed, 1157 insertions(+), 353 deletions(-) create mode 100644 src/interface/efi/efi_hii.c diff --git a/src/include/ipxe/efi/efi_hii.h b/src/include/ipxe/efi/efi_hii.h index f95a75f6..8e94bbe7 100644 --- a/src/include/ipxe/efi/efi_hii.h +++ b/src/include/ipxe/efi/efi_hii.h @@ -8,138 +8,89 @@ FILE_LICENCE ( GPL2_OR_LATER ); +#include #include #include -/** - * Define an EFI IFR form set type - * - * @v num_class_guids Number of class GUIDs - * @ret type Form set type - */ -#define EFI_IFR_FORM_SET_TYPE( num_class_guids ) \ - struct { \ - EFI_IFR_FORM_SET FormSet; \ - EFI_GUID ClassGuid[num_class_guids]; \ - } __attribute__ (( packed )) - -/** - * Define an EFI IFR form set - * - * @v guid GUID - * @v title Title string - * @v help Help string - * @v type Form set type (as returned by EFI_IFR_FORM_SET_TYPE()) - * @ret ifr Form set - * - * This definition opens a new scope, which must be closed by an - * EFI_IFR_END(). - */ -#define EFI_IFR_FORM_SET( guid, title, help, type, ... ) { \ - .FormSet = { \ - .Header = { \ - .OpCode = EFI_IFR_FORM_SET_OP, \ - .Length = sizeof ( type ), \ - .Scope = 1, \ - }, \ - .Guid = guid, \ - .FormSetTitle = title, \ - .Help = help, \ - .Flags = ( sizeof ( ( ( type * ) NULL )->ClassGuid ) / \ - sizeof ( ( ( type * ) NULL )->ClassGuid[0] ) ), \ - }, \ - .ClassGuid = { \ - __VA_ARGS__ \ - }, \ - } - -/** - * Define an EFI IFR GUID class - * - * @v class Class - * @ret ifr GUID class - */ -#define EFI_IFR_GUID_CLASS( class ) { \ - .Header = { \ - .OpCode = EFI_IFR_GUID_OP, \ - .Length = sizeof ( EFI_IFR_GUID_CLASS ), \ - }, \ - .Guid = EFI_IFR_TIANO_GUID, \ - .ExtendOpCode = EFI_IFR_EXTEND_OP_CLASS, \ - .Class = class, \ - } - -/** - * Define an EFI IFR GUID subclass - * - * @v subclass Subclass - * @ret ifr GUID subclass - */ -#define EFI_IFR_GUID_SUBCLASS( subclass ) { \ - .Header = { \ - .OpCode = EFI_IFR_GUID_OP, \ - .Length = sizeof ( EFI_IFR_GUID_SUBCLASS ), \ - }, \ - .Guid = EFI_IFR_TIANO_GUID, \ - .ExtendOpCode = EFI_IFR_EXTEND_OP_SUBCLASS, \ - .SubClass = subclass, \ - } - -/** - * Define an EFI IFR form - * - * @v formid Form ID - * @v title Title string - * @ret ifr Form - * - * This definition opens a new scope, which must be closed by an - * EFI_IFR_END(). - */ -#define EFI_IFR_FORM( formid, title ) { \ - .Header = { \ - .OpCode = EFI_IFR_FORM_OP, \ - .Length = sizeof ( EFI_IFR_FORM ), \ - .Scope = 1, \ - }, \ - .FormId = formid, \ - .FormTitle = title, \ - } - -/** - * Define an EFI IFR text widget - * - * @v prompt Prompt string - * @v help Help string - * @v text Text string - * @ret ifr Text widget - */ -#define EFI_IFR_TEXT( prompt, help, text ) { \ - .Header = { \ - .OpCode = EFI_IFR_TEXT_OP, \ - .Length = sizeof ( EFI_IFR_TEXT ), \ - }, \ - .Statement = { \ - .Prompt = prompt, \ - .Help = help, \ - }, \ - .TextTwo = text, \ - } - -/** - * Define an EFI IFR end marker - * - * @ret ifr End marker - */ -#define EFI_IFR_END() { \ - .Header = { \ - .OpCode = EFI_IFR_END_OP, \ - .Length = sizeof ( EFI_IFR_END ), \ - }, \ - } - /** GUID indicating formset compliance for IBM Unified Configuration Manager */ #define EFI_HII_IBM_UCM_COMPLIANT_FORMSET_GUID \ { 0x5c8e9746, 0xa5f7, 0x4593, \ { 0xaf, 0x1f, 0x66, 0xa8, 0x2a, 0xa1, 0x9c, 0xb1 } } +/** An EFI IFR builder */ +struct efi_ifr_builder { + /** IFR opcodes */ + EFI_IFR_OP_HEADER *ops; + /** Length of IFR opcodes */ + size_t ops_len; + /** Strings */ + EFI_HII_STRING_BLOCK *strings; + /** Length of strings */ + size_t strings_len; + /** Current string identifier */ + unsigned int string_id; + /** Current variable store identifier */ + unsigned int varstore_id; + /** Current form identifier */ + unsigned int form_id; + /** An allocation has failed */ + int failed; +}; + +/** + * Initialise IFR builder + * + * @v ifr IFR builder + * + * The caller must eventually call efi_ifr_free() to free the dynamic + * storage associated with the IFR builder. + */ +static inline void efi_ifr_init ( struct efi_ifr_builder *ifr ) { + memset ( ifr, 0, sizeof ( *ifr ) ); +} + +extern unsigned int efi_ifr_string ( struct efi_ifr_builder *ifr, + const char *fmt, ... ); +extern void efi_ifr_end_op ( struct efi_ifr_builder *ifr ); +extern void efi_ifr_false_op ( struct efi_ifr_builder *ifr ); +extern unsigned int efi_ifr_form_op ( struct efi_ifr_builder *ifr, + unsigned int title_id ); +extern void efi_ifr_form_set_op ( struct efi_ifr_builder *ifr, + const EFI_GUID *guid, + unsigned int title_id, unsigned int help_id, + ... ); +void efi_ifr_get_op ( struct efi_ifr_builder *ifr, unsigned int varstore_id, + unsigned int varstore_info, unsigned int varstore_type ); +extern void efi_ifr_guid_class_op ( struct efi_ifr_builder *ifr, + unsigned int class ); +extern void efi_ifr_guid_subclass_op ( struct efi_ifr_builder *ifr, + unsigned int subclass ); +extern void efi_ifr_numeric_op ( struct efi_ifr_builder *ifr, + unsigned int prompt_id, + unsigned int help_id, unsigned int question_id, + unsigned int varstore_id, + unsigned int varstore_info, + unsigned int vflags, unsigned long min_value, + unsigned long max_value, unsigned int step, + unsigned int flags ); +extern void efi_ifr_string_op ( struct efi_ifr_builder *ifr, + unsigned int prompt_id, unsigned int help_id, + unsigned int question_id, + unsigned int varstore_id, + unsigned int varstore_info, unsigned int vflags, + unsigned int min_size, unsigned int max_size, + unsigned int flags ); +extern void efi_ifr_suppress_if_op ( struct efi_ifr_builder *ifr ); +extern void efi_ifr_text_op ( struct efi_ifr_builder *ifr, + unsigned int prompt_id, unsigned int help_id, + unsigned int text_id ); +extern void efi_ifr_true_op ( struct efi_ifr_builder *ifr ); +extern unsigned int +efi_ifr_varstore_name_value_op ( struct efi_ifr_builder *ifr, + const EFI_GUID *guid ); +extern void efi_ifr_free ( struct efi_ifr_builder *ifr ); +extern EFI_HII_PACKAGE_LIST_HEADER * +efi_ifr_package ( struct efi_ifr_builder *ifr, const EFI_GUID *guid, + const char *language, unsigned int language_id ); + #endif /* _IPXE_EFI_HII_H */ diff --git a/src/interface/efi/efi_hii.c b/src/interface/efi/efi_hii.c new file mode 100644 index 00000000..834060b5 --- /dev/null +++ b/src/interface/efi/efi_hii.c @@ -0,0 +1,577 @@ +/* + * Copyright (C) 2012 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. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include +#include +#include +#include +#include +#include +#include + +/** Tiano GUID */ +static const EFI_GUID tiano_guid = EFI_IFR_TIANO_GUID; + +/** + * Add string to IFR builder + * + * @v ifr IFR builder + * @v fmt Format string + * @v ... Arguments + * @ret string_id String identifier, or zero on failure + */ +unsigned int efi_ifr_string ( struct efi_ifr_builder *ifr, const char *fmt, + ... ) { + EFI_HII_STRING_BLOCK *new_strings; + EFI_HII_SIBT_STRING_UCS2_BLOCK *ucs2; + size_t new_strings_len; + va_list args; + size_t len; + unsigned int string_id; + + /* Do nothing if a previous allocation has failed */ + if ( ifr->failed ) + return 0; + + /* Calculate string length */ + va_start ( args, fmt ); + len = ( efi_vsnprintf ( NULL, 0, fmt, args ) + 1 /* wNUL */ ); + va_end ( args ); + + /* Reallocate strings */ + new_strings_len = ( ifr->strings_len + + offsetof ( typeof ( *ucs2 ), StringText ) + + ( len * sizeof ( ucs2->StringText[0] ) ) ); + new_strings = realloc ( ifr->strings, new_strings_len ); + if ( ! new_strings ) { + ifr->failed = 1; + return 0; + } + ucs2 = ( ( ( void * ) new_strings ) + ifr->strings_len ); + ifr->strings = new_strings; + ifr->strings_len = new_strings_len; + + /* Fill in string */ + ucs2->Header.BlockType = EFI_HII_SIBT_STRING_UCS2; + va_start ( args, fmt ); + efi_vsnprintf ( ucs2->StringText, len, fmt, args ); + va_end ( args ); + + /* Allocate string ID */ + string_id = ++(ifr->string_id); + + DBGC ( ifr, "IFR %p string %#04x is \"%ls\"\n", + ifr, string_id, ucs2->StringText ); + return string_id; +} + +/** + * Add IFR opcode to IFR builder + * + * @v ifr IFR builder + * @v opcode Opcode + * @v len Opcode length + * @ret op Opcode, or NULL + */ +static void * efi_ifr_op ( struct efi_ifr_builder *ifr, unsigned int opcode, + size_t len ) { + EFI_IFR_OP_HEADER *new_ops; + EFI_IFR_OP_HEADER *op; + size_t new_ops_len; + + /* Do nothing if a previous allocation has failed */ + if ( ifr->failed ) + return NULL; + + /* Reallocate opcodes */ + new_ops_len = ( ifr->ops_len + len ); + new_ops = realloc ( ifr->ops, new_ops_len ); + if ( ! new_ops ) { + ifr->failed = 1; + return NULL; + } + op = ( ( ( void * ) new_ops ) + ifr->ops_len ); + ifr->ops = new_ops; + ifr->ops_len = new_ops_len; + + /* Fill in opcode header */ + op->OpCode = opcode; + op->Length = len; + + return op; +} + +/** + * Add end opcode to IFR builder + * + * @v ifr IFR builder + */ +void efi_ifr_end_op ( struct efi_ifr_builder *ifr ) { + size_t dispaddr = ifr->ops_len; + EFI_IFR_END *end; + + /* Add opcode */ + end = efi_ifr_op ( ifr, EFI_IFR_END_OP, sizeof ( *end ) ); + + DBGC ( ifr, "IFR %p end\n", ifr ); + DBGC2_HDA ( ifr, dispaddr, end, sizeof ( *end ) ); +} + +/** + * Add false opcode to IFR builder + * + * @v ifr IFR builder + */ +void efi_ifr_false_op ( struct efi_ifr_builder *ifr ) { + size_t dispaddr = ifr->ops_len; + EFI_IFR_FALSE *false; + + /* Add opcode */ + false = efi_ifr_op ( ifr, EFI_IFR_FALSE_OP, sizeof ( *false ) ); + + DBGC ( ifr, "IFR %p false\n", ifr ); + DBGC2_HDA ( ifr, dispaddr, false, sizeof ( *false ) ); +} + +/** + * Add form opcode to IFR builder + * + * @v ifr IFR builder + * @v title_id Title string identifier + * @ret form_id Form identifier + */ +unsigned int efi_ifr_form_op ( struct efi_ifr_builder *ifr, + unsigned int title_id ) { + size_t dispaddr = ifr->ops_len; + EFI_IFR_FORM *form; + + /* Add opcode */ + form = efi_ifr_op ( ifr, EFI_IFR_FORM_OP, sizeof ( *form ) ); + if ( ! form ) + return 0; + form->Header.Scope = 1; + form->FormId = ++(ifr->form_id); + form->FormTitle = title_id; + + DBGC ( ifr, "IFR %p name/value store %#04x title %#04x\n", + ifr, form->FormId, title_id ); + DBGC2_HDA ( ifr, dispaddr, form, sizeof ( *form ) ); + return form->FormId; +} + +/** + * Add formset opcode to IFR builder + * + * @v ifr IFR builder + * @v guid GUID + * @v title_id Title string identifier + * @v help_id Help string identifier + * @v ... Class GUIDs (terminated by NULL) + */ +void efi_ifr_form_set_op ( struct efi_ifr_builder *ifr, const EFI_GUID *guid, + unsigned int title_id, unsigned int help_id, ... ) { + size_t dispaddr = ifr->ops_len; + EFI_IFR_FORM_SET *formset; + EFI_GUID *class_guid; + unsigned int num_class_guids = 0; + size_t len; + va_list args; + + /* Count number of class GUIDs */ + va_start ( args, help_id ); + while ( va_arg ( args, const EFI_GUID * ) != NULL ) + num_class_guids++; + va_end ( args ); + + /* Add opcode */ + len = ( sizeof ( *formset ) + + ( num_class_guids * sizeof ( *class_guid ) ) ); + formset = efi_ifr_op ( ifr, EFI_IFR_FORM_SET_OP, len ); + if ( ! formset ) + return; + formset->Header.Scope = 1; + memcpy ( &formset->Guid, guid, sizeof ( formset->Guid ) ); + formset->FormSetTitle = title_id; + formset->Help = help_id; + formset->Flags = num_class_guids; + + /* Add class GUIDs */ + class_guid = ( ( ( void * ) formset ) + sizeof ( *formset ) ); + va_start ( args, help_id ); + while ( num_class_guids-- ) { + memcpy ( class_guid++, va_arg ( args, const EFI_GUID * ), + sizeof ( *class_guid ) ); + } + va_end ( args ); + + DBGC ( ifr, "IFR %p formset title %#04x help %#04x\n", + ifr, title_id, help_id ); + DBGC2_HDA ( ifr, dispaddr, formset, len ); +} + +/** + * Add get opcode to IFR builder + * + * @v ifr IFR builder + * @v varstore_id Variable store identifier + * @v varstore_info Variable string identifier or offset + * @v varstore_type Variable type + */ +void efi_ifr_get_op ( struct efi_ifr_builder *ifr, unsigned int varstore_id, + unsigned int varstore_info, unsigned int varstore_type ) { + size_t dispaddr = ifr->ops_len; + EFI_IFR_GET *get; + + /* Add opcode */ + get = efi_ifr_op ( ifr, EFI_IFR_GET_OP, sizeof ( *get ) ); + get->VarStoreId = varstore_id; + get->VarStoreInfo.VarName = varstore_info; + get->VarStoreType = varstore_type; + + DBGC ( ifr, "IFR %p get varstore %#04x:%#04x type %#02x\n", + ifr, varstore_id, varstore_info, varstore_type ); + DBGC2_HDA ( ifr, dispaddr, get, sizeof ( *get ) ); +} + +/** + * Add GUID class opcode to IFR builder + * + * @v ifr IFR builder + * @v class Class + */ +void efi_ifr_guid_class_op ( struct efi_ifr_builder *ifr, unsigned int class ) { + size_t dispaddr = ifr->ops_len; + EFI_IFR_GUID_CLASS *guid_class; + + /* Add opcode */ + guid_class = efi_ifr_op ( ifr, EFI_IFR_GUID_OP, + sizeof ( *guid_class ) ); + if ( ! guid_class ) + return; + memcpy ( &guid_class->Guid, &tiano_guid, sizeof ( guid_class->Guid ) ); + guid_class->ExtendOpCode = EFI_IFR_EXTEND_OP_CLASS; + guid_class->Class = class; + + DBGC ( ifr, "IFR %p GUID class %#02x\n", ifr, class ); + DBGC2_HDA ( ifr, dispaddr, guid_class, sizeof ( *guid_class ) ); +} + +/** + * Add GUID subclass opcode to IFR builder + * + * @v ifr IFR builder + * @v subclass Subclass + */ +void efi_ifr_guid_subclass_op ( struct efi_ifr_builder *ifr, + unsigned int subclass ) { + size_t dispaddr = ifr->ops_len; + EFI_IFR_GUID_SUBCLASS *guid_subclass; + + /* Add opcode */ + guid_subclass = efi_ifr_op ( ifr, EFI_IFR_GUID_OP, + sizeof ( *guid_subclass ) ); + if ( ! guid_subclass ) + return; + memcpy ( &guid_subclass->Guid, &tiano_guid, + sizeof ( guid_subclass->Guid ) ); + guid_subclass->ExtendOpCode = EFI_IFR_EXTEND_OP_SUBCLASS; + guid_subclass->SubClass = subclass; + + DBGC ( ifr, "IFR %p GUID subclass %#02x\n", ifr, subclass ); + DBGC2_HDA ( ifr, dispaddr, guid_subclass, sizeof ( *guid_subclass ) ); +} + +/** + * Add numeric opcode to IFR builder + * + * @v ifr IFR builder + * @v prompt_id Prompt string identifier + * @v help_id Help string identifier + * @v question_id Question identifier + * @v varstore_id Variable store identifier + * @v varstore_info Variable string identifier or offset + * @v vflags Variable flags + * @v min_value Minimum value + * @v max_value Maximum value + * @v step Step + * @v flags Flags + */ +void efi_ifr_numeric_op ( struct efi_ifr_builder *ifr, unsigned int prompt_id, + unsigned int help_id, unsigned int question_id, + unsigned int varstore_id, unsigned int varstore_info, + unsigned int vflags, unsigned long min_value, + unsigned long max_value, unsigned int step, + unsigned int flags ) { + size_t dispaddr = ifr->ops_len; + EFI_IFR_NUMERIC *numeric; + unsigned int size; + + /* Add opcode */ + numeric = efi_ifr_op ( ifr, EFI_IFR_NUMERIC_OP, sizeof ( *numeric ) ); + if ( ! numeric ) + return; + numeric->Question.Header.Prompt = prompt_id; + numeric->Question.Header.Help = help_id; + numeric->Question.QuestionId = question_id; + numeric->Question.VarStoreId = varstore_id; + numeric->Question.VarStoreInfo.VarName = varstore_info; + numeric->Question.Flags = vflags; + size = ( flags & EFI_IFR_NUMERIC_SIZE ); + switch ( size ) { + case EFI_IFR_NUMERIC_SIZE_1 : + numeric->data.u8.MinValue = min_value; + numeric->data.u8.MaxValue = max_value; + numeric->data.u8.Step = step; + break; + case EFI_IFR_NUMERIC_SIZE_2 : + numeric->data.u16.MinValue = min_value; + numeric->data.u16.MaxValue = max_value; + numeric->data.u16.Step = step; + break; + case EFI_IFR_NUMERIC_SIZE_4 : + numeric->data.u32.MinValue = min_value; + numeric->data.u32.MaxValue = max_value; + numeric->data.u32.Step = step; + break; + case EFI_IFR_NUMERIC_SIZE_8 : + numeric->data.u64.MinValue = min_value; + numeric->data.u64.MaxValue = max_value; + numeric->data.u64.Step = step; + break; + } + + DBGC ( ifr, "IFR %p numeric prompt %#04x help %#04x question %#04x " + "varstore %#04x:%#04x\n", ifr, prompt_id, help_id, question_id, + varstore_id, varstore_info ); + DBGC2_HDA ( ifr, dispaddr, numeric, sizeof ( *numeric ) ); +} + +/** + * Add string opcode to IFR builder + * + * @v ifr IFR builder + * @v prompt_id Prompt string identifier + * @v help_id Help string identifier + * @v question_id Question identifier + * @v varstore_id Variable store identifier + * @v varstore_info Variable string identifier or offset + * @v vflags Variable flags + * @v min_size Minimum size + * @v max_size Maximum size + * @v flags Flags + */ +void efi_ifr_string_op ( struct efi_ifr_builder *ifr, unsigned int prompt_id, + unsigned int help_id, unsigned int question_id, + unsigned int varstore_id, unsigned int varstore_info, + unsigned int vflags, unsigned int min_size, + unsigned int max_size, unsigned int flags ) { + size_t dispaddr = ifr->ops_len; + EFI_IFR_STRING *string; + + /* Add opcode */ + string = efi_ifr_op ( ifr, EFI_IFR_STRING_OP, sizeof ( *string ) ); + if ( ! string ) + return; + string->Question.Header.Prompt = prompt_id; + string->Question.Header.Help = help_id; + string->Question.QuestionId = question_id; + string->Question.VarStoreId = varstore_id; + string->Question.VarStoreInfo.VarName = varstore_info; + string->Question.Flags = vflags; + string->MinSize = min_size; + string->MaxSize = max_size; + string->Flags = flags; + + DBGC ( ifr, "IFR %p string prompt %#04x help %#04x question %#04x " + "varstore %#04x:%#04x\n", ifr, prompt_id, help_id, question_id, + varstore_id, varstore_info ); + DBGC2_HDA ( ifr, dispaddr, string, sizeof ( *string ) ); +} + +/** + * Add suppress-if opcode to IFR builder + * + * @v ifr IFR builder + */ +void efi_ifr_suppress_if_op ( struct efi_ifr_builder *ifr ) { + size_t dispaddr = ifr->ops_len; + EFI_IFR_SUPPRESS_IF *suppress_if; + + /* Add opcode */ + suppress_if = efi_ifr_op ( ifr, EFI_IFR_SUPPRESS_IF_OP, + sizeof ( *suppress_if ) ); + suppress_if->Header.Scope = 1; + + DBGC ( ifr, "IFR %p suppress-if\n", ifr ); + DBGC2_HDA ( ifr, dispaddr, suppress_if, sizeof ( *suppress_if ) ); +} + +/** + * Add text opcode to IFR builder + * + * @v ifr IFR builder + * @v prompt_id Prompt string identifier + * @v help_id Help string identifier + * @v text_id Text string identifier + */ +void efi_ifr_text_op ( struct efi_ifr_builder *ifr, unsigned int prompt_id, + unsigned int help_id, unsigned int text_id ) { + size_t dispaddr = ifr->ops_len; + EFI_IFR_TEXT *text; + + /* Add opcode */ + text = efi_ifr_op ( ifr, EFI_IFR_TEXT_OP, sizeof ( *text ) ); + if ( ! text ) + return; + text->Statement.Prompt = prompt_id; + text->Statement.Help = help_id; + text->TextTwo = text_id; + + DBGC ( ifr, "IFR %p text prompt %#04x help %#04x text %#04x\n", + ifr, prompt_id, help_id, text_id ); + DBGC2_HDA ( ifr, dispaddr, text, sizeof ( *text ) ); +} + +/** + * Add true opcode to IFR builder + * + * @v ifr IFR builder + */ +void efi_ifr_true_op ( struct efi_ifr_builder *ifr ) { + size_t dispaddr = ifr->ops_len; + EFI_IFR_TRUE *true; + + /* Add opcode */ + true = efi_ifr_op ( ifr, EFI_IFR_TRUE_OP, sizeof ( *true ) ); + + DBGC ( ifr, "IFR %p true\n", ifr ); + DBGC2_HDA ( ifr, dispaddr, true, sizeof ( *true ) ); +} + +/** + * Add name/value store opcode to IFR builder + * + * @v ifr IFR builder + * @v guid GUID + * @ret varstore_id Variable store identifier, or 0 on failure + */ +unsigned int efi_ifr_varstore_name_value_op ( struct efi_ifr_builder *ifr, + const EFI_GUID *guid ) { + size_t dispaddr = ifr->ops_len; + EFI_IFR_VARSTORE_NAME_VALUE *varstore; + + /* Add opcode */ + varstore = efi_ifr_op ( ifr, EFI_IFR_VARSTORE_NAME_VALUE_OP, + sizeof ( *varstore ) ); + if ( ! varstore ) + return 0; + varstore->VarStoreId = ++(ifr->varstore_id); + memcpy ( &varstore->Guid, guid, sizeof ( varstore->Guid ) ); + + DBGC ( ifr, "IFR %p name/value store %#04x\n", + ifr, varstore->VarStoreId ); + DBGC2_HDA ( ifr, dispaddr, varstore, sizeof ( *varstore ) ); + return varstore->VarStoreId; +} + +/** + * Free memory used by IFR builder + * + * @v ifr IFR builder + */ +void efi_ifr_free ( struct efi_ifr_builder *ifr ) { + + free ( ifr->ops ); + free ( ifr->strings ); + memset ( ifr, 0, sizeof ( *ifr ) ); +} + +/** + * Construct package list from IFR builder + * + * @v ifr IFR builder + * @v guid Package GUID + * @v language Language + * @v language_id Language string ID + * @ret package Package list, or NULL + * + * The package list is allocated using malloc(), and must eventually + * be freed by the caller. (The caller must also call efi_ifr_free() + * to free the temporary storage used during construction.) + */ +EFI_HII_PACKAGE_LIST_HEADER * efi_ifr_package ( struct efi_ifr_builder *ifr, + const EFI_GUID *guid, + const char *language, + unsigned int language_id ) { + struct { + EFI_HII_PACKAGE_LIST_HEADER header; + struct { + EFI_HII_PACKAGE_HEADER header; + uint8_t data[ifr->ops_len]; + } __attribute__ (( packed )) ops; + struct { + union { + EFI_HII_STRING_PACKAGE_HDR header; + uint8_t pad[offsetof(EFI_HII_STRING_PACKAGE_HDR, + Language) + + strlen ( language ) + 1 /* NUL */ ]; + } __attribute__ (( packed )) header; + uint8_t data[ifr->strings_len]; + EFI_HII_STRING_BLOCK end; + } __attribute__ (( packed )) strings; + EFI_HII_PACKAGE_HEADER end; + } __attribute__ (( packed )) *package; + + /* Fail if any previous allocation failed */ + if ( ifr->failed ) + return NULL; + + /* Allocate package list */ + package = zalloc ( sizeof ( *package ) ); + if ( ! package ) + return NULL; + + /* Populate package list */ + package->header.PackageLength = sizeof ( *package ); + memcpy ( &package->header.PackageListGuid, guid, + sizeof ( package->header.PackageListGuid ) ); + package->ops.header.Length = sizeof ( package->ops ); + package->ops.header.Type = EFI_HII_PACKAGE_FORMS; + memcpy ( package->ops.data, ifr->ops, sizeof ( package->ops.data ) ); + package->strings.header.header.Header.Length = + sizeof ( package->strings ); + package->strings.header.header.Header.Type = + EFI_HII_PACKAGE_STRINGS; + package->strings.header.header.HdrSize = + sizeof ( package->strings.header ); + package->strings.header.header.StringInfoOffset = + sizeof ( package->strings.header ); + package->strings.header.header.LanguageName = language_id; + strcpy ( package->strings.header.header.Language, language ); + memcpy ( package->strings.data, ifr->strings, + sizeof ( package->strings.data ) ); + package->strings.end.BlockType = EFI_HII_SIBT_END; + package->end.Type = EFI_HII_PACKAGE_END; + package->end.Length = sizeof ( package->end ); + + return &package->header; +} + diff --git a/src/interface/efi/efi_snp_hii.c b/src/interface/efi/efi_snp_hii.c index c02233db..96e9aa20 100644 --- a/src/interface/efi/efi_snp_hii.c +++ b/src/interface/efi/efi_snp_hii.c @@ -19,9 +19,39 @@ FILE_LICENCE ( GPL2_OR_LATER ); +/** + * @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 +#include #include +#include +#include #include +#include +#include #include #include #include @@ -34,250 +64,452 @@ FILE_LICENCE ( GPL2_OR_LATER ); static EFI_GUID efi_hii_config_access_protocol_guid = EFI_HII_CONFIG_ACCESS_PROTOCOL_GUID; +/** 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_REQUIRE_PROTOCOL ( EFI_HII_DATABASE_PROTOCOL, &efihii ); -/** Local base GUID used for our EFI SNP formset */ -#define EFI_SNP_FORMSET_GUID_BASE \ - { 0xc4f84019, 0x6dfd, 0x4a27, \ - { 0x9b, 0x94, 0xb7, 0x2e, 0x1f, 0xbc, 0xad, 0xca } } +/** + * 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 ){ -/** Form identifiers used for our EFI SNP HII */ -enum efi_snp_hii_form_id { - EFI_SNP_FORM = 0x0001, /**< The only form */ -}; - -/** String identifiers used for our EFI SNP HII */ -enum efi_snp_hii_string_id { - /* Language name */ - EFI_SNP_LANGUAGE_NAME = 0x0001, - /* Formset */ - EFI_SNP_FORMSET_TITLE, EFI_SNP_FORMSET_HELP, - /* Product name */ - EFI_SNP_PRODUCT_PROMPT, EFI_SNP_PRODUCT_HELP, EFI_SNP_PRODUCT_TEXT, - /* Version */ - EFI_SNP_VERSION_PROMPT, EFI_SNP_VERSION_HELP, EFI_SNP_VERSION_TEXT, - /* Driver */ - EFI_SNP_DRIVER_PROMPT, EFI_SNP_DRIVER_HELP, EFI_SNP_DRIVER_TEXT, - /* Device */ - EFI_SNP_DEVICE_PROMPT, EFI_SNP_DEVICE_HELP, EFI_SNP_DEVICE_TEXT, - /* End of list */ - EFI_SNP_MAX_STRING_ID -}; - -/** EFI SNP formset */ -struct efi_snp_formset { - EFI_HII_PACKAGE_HEADER Header; - EFI_IFR_FORM_SET_TYPE(2) FormSet; - EFI_IFR_GUID_CLASS Class; - EFI_IFR_GUID_SUBCLASS SubClass; - EFI_IFR_FORM Form; - EFI_IFR_TEXT ProductText; - EFI_IFR_TEXT VersionText; - EFI_IFR_TEXT DriverText; - EFI_IFR_TEXT DeviceText; - EFI_IFR_END EndForm; - EFI_IFR_END EndFormSet; -} __attribute__ (( packed )) efi_snp_formset = { - .Header = { - .Length = sizeof ( efi_snp_formset ), - .Type = EFI_HII_PACKAGE_FORMS, - }, - .FormSet = EFI_IFR_FORM_SET ( EFI_SNP_FORMSET_GUID_BASE, - EFI_SNP_FORMSET_TITLE, - EFI_SNP_FORMSET_HELP, - typeof ( efi_snp_formset.FormSet ), - EFI_HII_PLATFORM_SETUP_FORMSET_GUID, - EFI_HII_IBM_UCM_COMPLIANT_FORMSET_GUID ), - .Class = EFI_IFR_GUID_CLASS ( EFI_NETWORK_DEVICE_CLASS ), - .SubClass = EFI_IFR_GUID_SUBCLASS ( 0x03 ), - .Form = EFI_IFR_FORM ( EFI_SNP_FORM, EFI_SNP_FORMSET_TITLE ), - .ProductText = EFI_IFR_TEXT ( EFI_SNP_PRODUCT_PROMPT, - EFI_SNP_PRODUCT_HELP, - EFI_SNP_PRODUCT_TEXT ), - .VersionText = EFI_IFR_TEXT ( EFI_SNP_VERSION_PROMPT, - EFI_SNP_VERSION_HELP, - EFI_SNP_VERSION_TEXT ), - .DriverText = EFI_IFR_TEXT ( EFI_SNP_DRIVER_PROMPT, - EFI_SNP_DRIVER_HELP, - EFI_SNP_DRIVER_TEXT ), - .DeviceText = EFI_IFR_TEXT ( EFI_SNP_DEVICE_PROMPT, - EFI_SNP_DEVICE_HELP, - EFI_SNP_DEVICE_TEXT ), - .EndForm = EFI_IFR_END(), - .EndFormSet = EFI_IFR_END(), -}; + return find_child_settings ( netdev_settings ( snpdev->netdev ), + NVO_SETTINGS_NAME ); +} /** - * Generate EFI SNP string + * Check whether or not setting is applicable * - * @v wbuf Buffer - * @v swlen Size of buffer (in wide characters) * @v snpdev SNP device - * @ret wlen Length of string (in wide characters) + * @v setting Setting + * @ret applies Setting applies */ -static int efi_snp_string ( wchar_t *wbuf, ssize_t swlen, - enum efi_snp_hii_string_id id, - struct efi_snp_device *snpdev ) { - struct net_device *netdev = snpdev->netdev; - struct device *dev = netdev->dev; +static int efi_snp_hii_setting_applies ( struct efi_snp_device *snpdev, + struct setting *setting ) { - switch ( id ) { - case EFI_SNP_LANGUAGE_NAME: - return efi_ssnprintf ( wbuf, swlen, "English" ); - case EFI_SNP_FORMSET_TITLE: - return efi_ssnprintf ( wbuf, swlen, "%s (%s)", - ( PRODUCT_NAME[0] ? - PRODUCT_NAME : PRODUCT_SHORT_NAME ), - netdev_addr ( netdev ) ); - case EFI_SNP_FORMSET_HELP: - return efi_ssnprintf ( wbuf, swlen, - "Configure " PRODUCT_SHORT_NAME ); - case EFI_SNP_PRODUCT_PROMPT: - return efi_ssnprintf ( wbuf, swlen, "Name" ); - case EFI_SNP_PRODUCT_HELP: - return efi_ssnprintf ( wbuf, swlen, "Firmware product name" ); - case EFI_SNP_PRODUCT_TEXT: - return efi_ssnprintf ( wbuf, swlen, "%s", - ( PRODUCT_NAME[0] ? - PRODUCT_NAME : PRODUCT_SHORT_NAME ) ); - case EFI_SNP_VERSION_PROMPT: - return efi_ssnprintf ( wbuf, swlen, "Version" ); - case EFI_SNP_VERSION_HELP: - return efi_ssnprintf ( wbuf, swlen, "Firmware version" ); - case EFI_SNP_VERSION_TEXT: - return efi_ssnprintf ( wbuf, swlen, VERSION ); - case EFI_SNP_DRIVER_PROMPT: - return efi_ssnprintf ( wbuf, swlen, "Driver" ); - case EFI_SNP_DRIVER_HELP: - return efi_ssnprintf ( wbuf, swlen, "Firmware driver" ); - case EFI_SNP_DRIVER_TEXT: - return efi_ssnprintf ( wbuf, swlen, "%s", dev->driver_name ); - case EFI_SNP_DEVICE_PROMPT: - return efi_ssnprintf ( wbuf, swlen, "Device" ); - case EFI_SNP_DEVICE_HELP: - return efi_ssnprintf ( wbuf, swlen, "Hardware device" ); - case EFI_SNP_DEVICE_TEXT: - return efi_ssnprintf ( wbuf, swlen, "%s", dev->name ); - default: - assert ( 0 ); - return 0; + 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; + 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; + name_id = efi_ifr_string ( ifr, "%s", setting->name ); + prompt_id = efi_ifr_string ( ifr, "%s", setting->description ); + help_id = efi_ifr_string ( ifr, "http://ipxe.org/cfg/%s", + 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 ); } } /** - * Generate EFI SNP string package - * - * @v strings String package header buffer - * @v max_len Buffer length - * @v snpdev SNP device - * @ret len Length of string package - */ -static int efi_snp_strings ( EFI_HII_STRING_PACKAGE_HDR *strings, - size_t max_len, struct efi_snp_device *snpdev ) { - static const char language[] = "en-us"; - void *buf = strings; - ssize_t remaining = max_len; - size_t hdrsize; - EFI_HII_SIBT_STRING_UCS2_BLOCK *string; - ssize_t wremaining; - size_t string_wlen; - unsigned int id; - EFI_HII_STRING_BLOCK *end; - size_t len; - - /* Calculate header size */ - hdrsize = ( offsetof ( typeof ( *strings ), Language ) + - sizeof ( language ) ); - buf += hdrsize; - remaining -= hdrsize; - - /* Fill in strings */ - for ( id = 1 ; id < EFI_SNP_MAX_STRING_ID ; id++ ) { - string = buf; - if ( remaining >= ( ( ssize_t ) sizeof ( string->Header ) ) ) - string->Header.BlockType = EFI_HII_SIBT_STRING_UCS2; - buf += offsetof ( typeof ( *string ), StringText ); - remaining -= offsetof ( typeof ( *string ), StringText ); - wremaining = ( remaining / - ( ( ssize_t ) sizeof ( string->StringText[0] ))); - assert ( ! ( ( remaining <= 0 ) && ( wremaining > 0 ) ) ); - string_wlen = efi_snp_string ( string->StringText, wremaining, - id, snpdev ); - buf += ( ( string_wlen + 1 /* wNUL */ ) * - sizeof ( string->StringText[0] ) ); - remaining -= ( ( string_wlen + 1 /* wNUL */ ) * - sizeof ( string->StringText[0] ) ); - } - - /* Fill in end marker */ - end = buf; - if ( remaining >= ( ( ssize_t ) sizeof ( *end ) ) ) - end->BlockType = EFI_HII_SIBT_END; - buf += sizeof ( *end ); - remaining -= sizeof ( *end ); - - /* Calculate overall length */ - len = ( max_len - remaining ); - - /* Fill in string package header */ - if ( strings ) { - memset ( strings, 0, sizeof ( *strings ) ); - strings->Header.Length = len; - strings->Header.Type = EFI_HII_PACKAGE_STRINGS; - strings->HdrSize = hdrsize; - strings->StringInfoOffset = hdrsize; - strings->LanguageName = EFI_SNP_LANGUAGE_NAME; - memcpy ( strings->Language, language, sizeof ( language ) ); - } - - return len; -} - -/** - * Generate EFI SNP package list + * Build HII package list for SNP device * * @v snpdev SNP device - * @ret package_list Package list, or NULL on error - * - * The package list is allocated using malloc(), and must eventually - * be freed by the caller. + * @ret package Package list, or NULL on error */ static EFI_HII_PACKAGE_LIST_HEADER * -efi_snp_package_list ( struct efi_snp_device *snpdev ) { - size_t strings_len = efi_snp_strings ( NULL, 0, snpdev ); - struct { - EFI_HII_PACKAGE_LIST_HEADER header; - struct efi_snp_formset formset; - union { - EFI_HII_STRING_PACKAGE_HDR strings; - uint8_t pad[strings_len]; - } __attribute__ (( packed )) strings; - EFI_HII_PACKAGE_HEADER end; - } __attribute__ (( packed )) *package_list; +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 *product_name; + EFI_GUID package_guid; + EFI_GUID formset_guid; + EFI_GUID varstore_guid; + unsigned int title_id; + unsigned int varstore_id; - /* Allocate package list */ - package_list = zalloc ( sizeof ( *package_list ) ); - if ( ! package_list ) + /* Initialise IFR builder */ + efi_ifr_init ( &ifr ); + + /* Determine product name */ + product_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)", product_name, + netdev_addr ( netdev ) ); + + /* Generate opcodes */ + efi_ifr_form_set_op ( &ifr, &formset_guid, title_id, + efi_ifr_string ( &ifr, + "Configure " 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", product_name ) ); + efi_ifr_text_op ( &ifr, + efi_ifr_string ( &ifr, "Version" ), + efi_ifr_string ( &ifr, "Firmware version" ), + efi_ifr_string ( &ifr, 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; + } - /* Create a unique GUID for this package list and formset */ - efi_snp_formset.FormSet.FormSet.Guid.Data1++; + /* Free temporary storage */ + efi_ifr_free ( &ifr ); + return package; +} - /* Populate package list */ - memcpy ( &package_list->header.PackageListGuid, - &efi_snp_formset.FormSet.FormSet.Guid, - sizeof ( package_list->header.PackageListGuid ) ); - package_list->header.PackageLength = sizeof ( *package_list ); - memcpy ( &package_list->formset, &efi_snp_formset, - sizeof ( package_list->formset ) ); - efi_snp_strings ( &package_list->strings.strings, - sizeof ( package_list->strings ), snpdev ); - package_list->end.Length = sizeof ( package_list->end ); - package_list->end.Type = EFI_HII_PACKAGE_END; +/** + * 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; + size_t len; + void *new; - return &package_list->header; + /* Allocate new string */ + len = ( ( *results ? ( wcslen ( *results ) + 1 /* "&" */ ) : 0 ) + + strlen ( key ) + 1 /* "=" */ + strlen ( value ) + 1 /* NUL */ ); + bs->AllocatePool ( EfiBootServicesData, ( len * sizeof ( wchar_t ) ), + &new ); + if ( ! new ) + return -ENOMEM; + + /* 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 setting *setting; + 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, 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 ( settings, setting, 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; } /** @@ -292,14 +524,47 @@ efi_snp_package_list ( struct efi_snp_device *snpdev ) { 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 __unused ) { + 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 \"%ls\"\n", snpdev, request ); + DBGC ( snpdev, "SNPDEV %p ExtractConfig request \"%ls\"\n", + snpdev, request ); - *progress = request; - return EFI_INVALID_PARAMETER; + /* Initialise results */ + *results = NULL; + + /* 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 RC_TO_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 rc; + } + } + } + + DBGC ( snpdev, "SNPDEV %p ExtractConfig results \"%ls\"\n", + snpdev, *results ); + return 0; } /** @@ -315,11 +580,22 @@ 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 ); - *progress = config; - return EFI_INVALID_PARAMETER; + /* 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 RC_TO_EFIRC ( rc ); + } + } + + return 0; } /** @@ -368,7 +644,7 @@ int efi_snp_hii_install ( struct efi_snp_device *snpdev ) { memcpy ( &snpdev->hii, &efi_snp_device_hii, sizeof ( snpdev->hii ) ); /* Create HII package list */ - snpdev->package_list = efi_snp_package_list ( snpdev ); + 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 );