]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[efi] Expose net device non-volatile settings via HII
authorMichael Brown <mcb30@ipxe.org>
Thu, 11 Oct 2012 14:02:50 +0000 (15:02 +0100)
committerMichael Brown <mcb30@ipxe.org>
Tue, 16 Oct 2012 14:10:52 +0000 (15:10 +0100)
Signed-off-by: Michael Brown <mcb30@ipxe.org>
src/include/ipxe/efi/efi_hii.h
src/interface/efi/efi_hii.c [new file with mode: 0644]
src/interface/efi/efi_snp_hii.c

index f95a75f609c3d861c9680b9349a7860a0d64cb81..8e94bbe7e3341aeee7838b12893c9d286d8bf43d 100644 (file)
 
 FILE_LICENCE ( GPL2_OR_LATER );
 
+#include <string.h>
 #include <ipxe/efi/Uefi/UefiInternalFormRepresentation.h>
 #include <ipxe/efi/Guid/MdeModuleHii.h>
 
-/**
- * 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,                                              \
-       }
+/** 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 } }
 
-/**
- * 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,                                                \
-       }
+/** 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;
+};
 
 /**
- * Define an EFI IFR text widget
+ * Initialise IFR builder
  *
- * @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
+ * @v ifr              IFR builder
  *
- * @ret ifr            End marker
+ * The caller must eventually call efi_ifr_free() to free the dynamic
+ * storage associated with the IFR builder.
  */
-#define EFI_IFR_END() {                                                           \
-       .Header = {                                                        \
-               .OpCode = EFI_IFR_END_OP,                                  \
-               .Length = sizeof ( EFI_IFR_END ),                          \
-       },                                                                 \
-       }
+static inline void efi_ifr_init ( struct efi_ifr_builder *ifr ) {
+       memset ( ifr, 0, sizeof ( *ifr ) );
+}
 
-/** 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 } }
+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 (file)
index 0000000..834060b
--- /dev/null
@@ -0,0 +1,577 @@
+/*
+ * 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.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdarg.h>
+#include <string.h>
+#include <ipxe/efi/efi.h>
+#include <ipxe/efi/efi_strings.h>
+#include <ipxe/efi/efi_hii.h>
+
+/** 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;
+}
+
index c02233db0c8c5706af72a12c7b76adc0cb7cb798..96e9aa20fabd3494d741f88cac9bcc162fd53989 100644 (file)
 
 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 <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/efi/efi.h>
@@ -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 ){
+
+       return find_child_settings ( netdev_settings ( snpdev->netdev ),
+                                    NVO_SETTINGS_NAME );
+}
 
-/** Form identifiers used for our EFI SNP HII */
-enum efi_snp_hii_form_id {
-       EFI_SNP_FORM = 0x0001,          /**< The only form */
-};
+/**
+ * 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 ) {
 
-/** 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
-};
+       return nvo_applies ( efi_snp_hii_settings ( snpdev ), setting );
+}
 
-/** 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(),
-};
+/**
+ * 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
+ * Build HII package list for SNP device
  *
- * @v wbuf             Buffer
- * @v swlen            Size of buffer (in wide characters)
  * @v snpdev           SNP device
- * @ret wlen           Length of string (in wide characters)
+ * @ret package                Package list, or NULL on error
  */
-static int efi_snp_string ( wchar_t *wbuf, ssize_t swlen,
-                           enum efi_snp_hii_string_id id,
-                           struct efi_snp_device *snpdev ) {
+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;
-
-       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;
+       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;
+
+       /* 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;
        }
+
+       /* Free temporary storage */
+       efi_ifr_free ( &ifr );
+       return package;
 }
 
 /**
- * Generate EFI SNP string package
+ * Append response to result string
  *
- * @v strings          String package header buffer
- * @v max_len          Buffer length
  * @v snpdev           SNP device
- * @ret len            Length of string package
+ * @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_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;
+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;
+
+       /* 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;
+       }
 
-       /* 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] ) );
+       /* 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 = "";
        }
 
-       /* 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 ) );
+       /* Append results */
+       if ( ( rc = efi_snp_hii_append ( snpdev, key, encoded,
+                                        results ) ) != 0 ) {
+               goto err_append;
        }
 
-       return len;
+       /* Success */
+       rc = 0;
+
+ err_append:
+       free ( buf );
+ err_alloc:
+ err_fetchf_len:
+ err_find_setting:
+ err_no_settings:
+       return rc;
 }
 
 /**
- * Generate EFI SNP package list
+ * Fetch HII setting
  *
  * @v snpdev           SNP device
- * @ret package_list   Package list, or NULL on error
+ * @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
  *
- * The package list is allocated using malloc(), and must eventually
- * be freed by the caller.
+ * @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 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;
-
-       /* Allocate package list */
-       package_list = zalloc ( sizeof ( *package_list ) );
-       if ( ! package_list )
-               return NULL;
+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;
+       }
 
-       /* Create a unique GUID for this package list and formset */
-       efi_snp_formset.FormSet.FormSet.Guid.Data1++;
-
-       /* 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;
-
-       return &package_list->header;
+       /* 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 request \"%ls\"\n",
+              snpdev, request );
+
+       /* 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 );
+               }
+       }
 
-       DBGC ( snpdev, "SNPDEV %p ExtractConfig \"%ls\"\n", snpdev, request );
+       /* 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;
+                       }
+               }
+       }
 
-       *progress = request;
-       return EFI_INVALID_PARAMETER;
+       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 );